diff -r 000000000000 -r fb279309251b project/com.nokia.carbide.cpp.epoc.engine/src/com/nokia/carbide/internal/cpp/epoc/engine/model/mmp/MMPView.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project/com.nokia.carbide.cpp.epoc.engine/src/com/nokia/carbide/internal/cpp/epoc/engine/model/mmp/MMPView.java Fri Apr 03 23:33:03 2009 +0100 @@ -0,0 +1,1331 @@ +/* +* Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: +* +*/ + +package com.nokia.carbide.internal.cpp.epoc.engine.model.mmp; + +import com.nokia.carbide.cpp.epoc.engine.EpocEnginePlugin; +import com.nokia.carbide.cpp.epoc.engine.image.IMultiImageSource; +import com.nokia.carbide.cpp.epoc.engine.model.mmp.*; +import com.nokia.carbide.cpp.epoc.engine.preprocessor.IDefine; +import com.nokia.carbide.internal.api.cpp.epoc.engine.dom.*; +import com.nokia.carbide.internal.api.cpp.epoc.engine.dom.mmp.*; +import com.nokia.carbide.internal.cpp.epoc.engine.model.*; +import com.nokia.carbide.internal.cpp.epoc.engine.parser.ITranslationUnitParser; +import com.nokia.carbide.internal.cpp.epoc.engine.parser.mmp.IMMPParserConfiguration; +import com.nokia.carbide.internal.cpp.epoc.engine.preprocessor.IConditionalBlock; +import com.nokia.cpp.internal.api.utils.core.*; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.osgi.framework.Version; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * View onto MMP contents. + * + */ +public class MMPView extends ViewASTBase implements IMMPView { + + static final String SOURCE_INFO_PATTERN = "SOURCE|SOURCEPATH"; //$NON-NLS-1$ + + static final String BITMAP_KEYWORD = "BITMAP"; //$NON-NLS-1$ + + static final String RESOURCE_KEYWORD = "RESOURCE"; //$NON-NLS-1$ + + private static final String DEF_FILE_EXTENSION = "def"; //$NON-NLS-1$ + private static final Pattern VERSION_PATTERN = Pattern.compile("(.*)\\{.*\\}"); //$NON-NLS-1$ + + private List sources; + private List documents; + private List userIncludes; + private List systemIncludes; + private Map> stringListArgumentSettings; + private List bitmaps; + private Set flagSettings; + private Map singleArgumentSettings; + private List resourceBlocks; + private List aifs; + + private List userResources; + private List systemResources; + private List languages; + private String uid2; + private String uid3; + private Map options, linkerOptions, replaceOptions; + + IPath defFileBase; + + private List knownStatements; + + protected OrderedObjectMap aifToStatementMap; + protected OrderedObjectMap resourceBlockToStatementMap; + protected OrderedObjectMap bitmapBlockToStatementMap; + + /** read-only list for now */ + private Set sourcePaths; + + public MMPView(ModelBase model, ITranslationUnitParser parser, + IMMPViewConfiguration viewConfiguration) { + super(model, parser, viewConfiguration); + + //System.out.println("Parsing view for " + model.getPath()); + + stringListArgumentSettings = new LinkedHashMap>(); + singleArgumentSettings = new LinkedHashMap(); + flagSettings = new LinkedHashSet(); + + for (EMMPStatement stmt : EMMPStatement.values()) { + if (((IMMPViewConfiguration) getViewConfiguration()).isStatementSupported(stmt)) { + switch (stmt.getCategory()) { + case IMMPParserConfiguration.LIST_ARGUMENT_STATEMENT: + if (isStringListArgumentSettingStatement(stmt)) { + stringListArgumentSettings.put(stmt, new ArrayList()); + } + break; + case IMMPParserConfiguration.SINGLE_ARGUMENT_STATEMENT: + if (isSingleArgumentSettingStatement(stmt)) { + singleArgumentSettings.put(stmt, null); + } + break; + } + } + } + + knownStatements = new ArrayList(); + + sources = new ArrayList(); + documents = new ArrayList(); + + userIncludes = new ArrayList(); + systemIncludes = new ArrayList(); + userResources = new ArrayList(); + systemResources = new ArrayList(); + resourceBlocks = new ArrayList(); + languages = new ArrayList(); + + bitmaps = new ArrayList(); + aifs = new ArrayList(); + options = new LinkedHashMap(); + linkerOptions = new LinkedHashMap(); + replaceOptions = new LinkedHashMap(); + + defFileBase = convertModelToProjectPath(new Path("")); // //$NON-NLS-1$ + + sourcePaths = new LinkedHashSet(); + + aifToStatementMap = new OrderedObjectMap(); + resourceBlockToStatementMap = new OrderedObjectMap(); + bitmapBlockToStatementMap = new OrderedObjectMap(); + } + + IASTMMPTranslationUnit mmp() { + return (IASTMMPTranslationUnit) getFilteredTranslationUnit(); + } + + /** + * Identify a path which should not be converted to be project-relative. + * This includes actual absolute paths and special EPOCROOT\epoc32 + * paths, which look relative but start with "+". + * @param mmpPath + * @return + */ + static boolean isAbsoluteLikePath(IPath mmpPath) { + return (mmpPath.isAbsolute() + || (mmpPath.segmentCount() > 0 && mmpPath.segment(0).equals("+"))); //$NON-NLS-1$ + } + + /** + * Convert an MMP-relative string filename to a project-relative path if possible. + * @param filename + * @return + */ + IPath fromMmpToProjectPath(IPath mmpPath) { + if (isAbsoluteLikePath(mmpPath)) + return mmpPath; + IPath path = currentDirectory != null ? currentDirectory.append(mmpPath) : mmpPath; + if (FileUtils.isPathInParent(path)) { + path = getProjectPath().append(path); + } + return path; + } + + /** + * Convert an MMP-relative string filename to a project-relative path if possible. + * @param filename + * @return + */ + IPath fromMmpToProjectPath(String mmpPath) { + return fromMmpToProjectPath(FileUtils.createPossiblyRelativePath(mmpPath)); + } + + /** + * Convert an MMP-relative string filename to a project-relative path + * if possible. + * @param mmpPath text node from MMP + * @return + */ + IPath fromMmpToProjectPath(IASTLiteralTextNode mmpPath) { + return fromMmpToProjectPath(mmpPath.getValue()); + } + + /** + * Convert a project-relative IPath to an MMP-relative path and + * leave other paths alone. + * @param projectPath + * @return MMP-relative directory (never blank) or self if not project-relative + */ + IPath fromProjectToMmpPath(IPath projectPath) { + if (isAbsoluteLikePath(projectPath)) + return projectPath; + IPath path = fromProjectToRelativePath(currentDirectory, projectPath); + if (path.segmentCount() == 0) + return new Path("."); //$NON-NLS-1$ + else + return path; + } + + + /** + * Create an EMMPLanguage list from the given LANG statement. + * @param prevLang + * @return + */ + public List createLanguageList(IASTMMPListArgumentStatement stmt) { + List list = new ArrayList(); + for (IASTLiteralTextNode node : stmt.getArguments()) { + try { + list.add(EMMPLanguage.fromCode(node.getValue())); + } catch (IllegalArgumentException e) { + // ignore unknown language + EpocEnginePlugin.log( + Logging.newStatus(EpocEnginePlugin.getDefault(), + new IllegalArgumentException( + "Warning, ignoring unknown language code: " + node.getValue() + " in " + stmt) + )); + } + } + return list; + } + + /** + * Create a LANG statement with the given languages. + * @param list + * @return + */ + public IASTMMPListArgumentStatement createLanguageStatement(Collection list) { + IASTListNode languages = ASTMMPFactory.createLiteralTextNodeList(); + for (EMMPLanguage lang : list) + languages.add(ASTMMPFactory.createPreprocessorLiteralTextNode(lang.getCodeString())); + return ASTMMPFactory.createMMPListArgumentStatement( + ASTMMPFactory.createPreprocessorLiteralTextNode(EMMPStatement.LANG.toString()), + languages); + } + + /** + * Tell if the statement is a list argument statement that we store + * in lists (rather than in some other manner). + * @return + */ + public boolean isStringListArgumentSettingStatement(EMMPStatement stmt) { + if (stmt.getCategory() != IMMPParserConfiguration.LIST_ARGUMENT_STATEMENT) + return false; + return stmt != EMMPStatement.SOURCE + && stmt != EMMPStatement.USERINCLUDE && stmt != EMMPStatement.SYSTEMINCLUDE + && stmt != EMMPStatement.RESOURCE && stmt != EMMPStatement.SYSTEMRESOURCE + && stmt != EMMPStatement.LANG && stmt != EMMPStatement.DOCUMENT; + } + + /** + * Tell if the statement is a single argument statement that we store + * in the single-argument settings (rather than in some other manner). + * @return true: yup + */ + public boolean isSingleArgumentSettingStatement(EMMPStatement stmt) { + if (stmt.getCategory() != IMMPParserConfiguration.SINGLE_ARGUMENT_STATEMENT) + return false; + return stmt != EMMPStatement.SOURCEPATH; + } + + /** + * Tell if the statement is a flag statement that we store in the + * flag settings + */ + public boolean isFlagSettingStatement(EMMPStatement stmt) { + return stmt.getCategory() == IMMPParserConfiguration.FLAG_STATEMENT; + } + + /** + * Get the default project-relative directory for source files. + * @return project-relative path, maybe empty if at project + */ + IPath defaultProjectRelativeSourcePath() { + return currentDirectory; + } + + /** + * Return an appropriate path for a SOURCEPATH statement + * @param mmpSourceElement filename + * @return the directory of the filename, or "." if at root + */ + IPath getSourcePathFromSource(IPath mmpSourceElement) { + return mmpSourceElement.segmentCount() > 1 ? mmpSourceElement.removeLastSegments(1) + : new Path("."); //$NON-NLS-1$ + } + + static public boolean equalPath(IPath path, IPath path2) { + if (path == null || path2 == null) + return false; + if (path.segmentCount() == 0 && path2.segmentCount() == 1 && path2.segment(0).equals(".")) { //$NON-NLS-1$ + return true; + } + if (path2.segmentCount() == 0 && path.segmentCount() == 1 && path.segment(0).equals(".")) { //$NON-NLS-1$ + return true; + } + //return path.toOSString().equalsIgnoreCase(path2.toOSString()); + // don't ignore capitalization, otherwise there's no way the client can use the API + // to repair it + return path.equals(path2); + } + + /** + * Tell whether a statement can be "seen" at a global level. + * This means anything at the top level and anything inside a START PLATFORM block. + * Stuff from other blocks are local to that block and not globally visible. + * @param stmt + * @return true if statement is global + */ + boolean isGloballyVisibleStatement(IASTStatement stmt) { + if (stmt.getParent() == mmp().getNodes()) + return true; + + // only handle nested statements in START PLATFORM. + IASTNode parent = stmt; + while ((parent = parent.getParent()) != null) { + if (parent instanceof IASTMMPStartBlockStatement) { + String blockType = ((IASTMMPStartBlockStatement) parent).getBlockType().getValue(); + if (blockType.equals(RESOURCE_KEYWORD) || blockType.equals(BITMAP_KEYWORD)) + return false; + return isMacroDefined(blockType.toUpperCase()); + } + } + + // what is this statement? + Check.checkState(false); + return false; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.ViewBase#internalRevertChanges() + */ + @Override + protected void internalRevertChanges() { + knownStatements.clear(); + + aifToStatementMap.clear(); + bitmapBlockToStatementMap.clear(); + resourceBlockToStatementMap.clear(); + + MMPStatementScanner scanner = new MMPStatementScanner(this); + + flagSettings.clear(); + singleArgumentSettings.clear(); + stringListArgumentSettings.clear(); + for (EMMPStatement stmt : EMMPStatement.values()) { + if (((IMMPViewConfiguration) getViewConfiguration()).isStatementSupported(stmt)) { + switch (stmt.getCategory()) { + case IMMPParserConfiguration.LIST_ARGUMENT_STATEMENT: + if (isStringListArgumentSettingStatement(stmt)) { + stringListArgumentSettings.put(stmt, new ArrayList()); + } + break; + case IMMPParserConfiguration.SINGLE_ARGUMENT_STATEMENT: + if (isSingleArgumentSettingStatement(stmt)) { + singleArgumentSettings.put(stmt, null); + } + break; + } + } + } + sources.clear(); + documents.clear(); + userIncludes.clear(); + systemIncludes.clear(); + bitmaps.clear(); + flagSettings.clear(); + resourceBlocks.clear(); + aifs.clear(); + userResources.clear(); + systemResources.clear(); + languages.clear(); + uid2 = null; + uid3 = null; + options.clear(); + linkerOptions.clear(); + replaceOptions.clear(); + + defFileBase = convertModelToProjectPath(new Path("")); // //$NON-NLS-1$ + + sourcePaths.clear(); + + for (IASTTopLevelNode tln : mmp().getNodes()) { + if (!(tln instanceof IASTMMPStatement)) + continue; + IASTMMPStatement statement = (IASTMMPStatement) tln; + scanner.scanStatement(statement); + + // record slash direction + recordSlashInfo(statement.getOriginalText()); + } + + if (debug) { + System.out.println("#internalRevertChanges() for " + getModel().getPath() + ":\n"+ //$NON-NLS-1$ //$NON-NLS-2$ + "Incoming filtered TU:\n"+ //$NON-NLS-1$ + getFilteredTranslationUnit().getNewText()); + dumpModelStructure(); + } + } + + + private void dumpModelStructure() { + dumpCollection("Sources", sources); //$NON-NLS-1$ + dumpCollection("Documents", documents); //$NON-NLS-1$ + dumpCollection("User Includes", userIncludes); //$NON-NLS-1$ + dumpCollection("System Includes", userIncludes); //$NON-NLS-1$ + dumpCollection("Flags", flagSettings); //$NON-NLS-1$ + dumpCollection("Single Arguments", singleArgumentSettings); //$NON-NLS-1$ + dumpCollection("String Lists", stringListArgumentSettings); //$NON-NLS-1$ + dumpCollection("Bitmaps", bitmaps); //$NON-NLS-1$ + dumpCollection("Resource Blocks", resourceBlocks); //$NON-NLS-1$ + dumpCollection("User Resources", userResources); //$NON-NLS-1$ + dumpCollection("System Resources", systemResources); //$NON-NLS-1$ + dumpCollection("Languages", languages); //$NON-NLS-1$ + dumpCollection("AIFs", aifs); //$NON-NLS-1$ + dumpCollection("Options", options); //$NON-NLS-1$ + dumpCollection("Linker Options", linkerOptions); //$NON-NLS-1$ + dumpCollection("Replace Options", replaceOptions); //$NON-NLS-1$ + System.out.println("DEF file: " + getSingleArgumentSettings().get(EMMPStatement.DEFFILE) + " in " + defFileBase); //$NON-NLS-1$ //$NON-NLS-2$ + System.out.println("UID: " + uid2 + " " + uid3); //$NON-NLS-1$ //$NON-NLS-2$ + dumpCollection("SourcePaths", sourcePaths); //$NON-NLS-1$ + } + + private void dumpCollection(String string, Collection coll) { + if (!coll.isEmpty()) { + System.out.println(string + ":"); //$NON-NLS-1$ + for (Object obj : coll) { + System.out.println("\t" + obj); //$NON-NLS-1$ + } + } + } + + private void dumpCollection(String string, Map map) { + if (!map.isEmpty()) { + System.out.println(string + ":"); //$NON-NLS-1$ + for (Map.Entry ent : (Set)map.entrySet()) { + if (ent.getValue() != null && !(ent.getValue() instanceof Collection && ((Collection)ent.getValue()).isEmpty())) + System.out.println("\t" + ent.getKey() + ": " + ent.getValue()); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.mmp.MMPView#internalGatherChanges(java.util.List, java.util.List) + */ + @Override + protected void internalGatherChanges(List modifications, List messages) { + if (debug) { + System.out.println("#internalGatherChanges for " + getModel().getPath() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$ + dumpModelStructure(); + } + + MMPStatementUpdater updater = new MMPStatementUpdater(this, modifications, messages); + updater.update(); + + if (debug) { + System.out.println( + "New filtered TU:\n"+ //$NON-NLS-1$ + getFilteredTranslationUnit().getNewText()); + + } + } + + private boolean statementUsesSourcePath(IASTMMPStatement statement) { + if (statement.getKeywordName() == null) + return false; + + if (statement.getKeywordName().matches("(?i)SOURCE|RESOURCE|SYSTEMRESOURCE|DOCUMENT")) {//$NON-NLS-1$ + return true; + } + if (statement instanceof IASTMMPStartBlockStatement) { + return "RESOURCE".equalsIgnoreCase(((IASTMMPStartBlockStatement) statement).getBlockType().getValue()); //$NON-NLS-1$ + } + return false; + } + + + private void deleteStatement(IASTNode node) { + ((IASTListHolder) node.getParent()).getList().remove(node); + } + + /** + * Remove stray SOURCEPATH statements + * + */ + private void cleanupSourcePaths(IASTTranslationUnit tu) { + IASTMMPStatement lastSourcePath = null; + IConditionalBlock lastSourcePathBlock = null; + boolean usedSourcePath = true; + + IASTTopLevelNode[] nodes = (IASTTopLevelNode[]) tu.getNodes().toArray(new IASTTopLevelNode[tu.getNodes().size()]); + for (IASTTopLevelNode node : nodes) { + if (!(node instanceof IASTMMPStatement)) + continue; + IASTMMPStatement stmt = (IASTMMPStatement) node; + IConditionalBlock block = getLangNodeConditionalBlock(stmt); + if (EMMPStatement.SOURCEPATH.matches(stmt)) { + if (!usedSourcePath + && (lastSourcePathBlock == block || (lastSourcePathBlock != null && lastSourcePathBlock.contains(block)))) { + deleteStatement(lastSourcePath); + } + lastSourcePath = stmt; + lastSourcePathBlock = block; + usedSourcePath = false; + } + else if (statementUsesSourcePath(stmt)) { + usedSourcePath = true; + } + } + + if (lastSourcePath != null && !usedSourcePath) { + deleteStatement(lastSourcePath); + } + } + + + private void addMissingSourcePaths(IASTTranslationUnit tu) { + IASTMMPSingleArgumentStatement lastSourcePathStatement = null; + updateCurrentDirectory(tu); + IPath lastSourcePath = defaultProjectRelativeSourcePath(); + + // scan top-level nodes (only -- not inside START or the like) to see + // if every statement that has a reference to a SOURCEPATH is still + // "seeing" the same node (or equivalent) + // + for (ListIterator iter = tu.getNodes().listIterator(); + iter.hasNext();) { + IASTTopLevelNode tln = iter.next(); + updateCurrentDirectory(tln); + if (!(tln instanceof IASTMMPStatement)) { + continue; + } + IASTMMPStatement stmt = (IASTMMPStatement) tln; + if (EMMPStatement.SOURCEPATH.matches(stmt)) { + lastSourcePathStatement = (IASTMMPSingleArgumentStatement) tln; + lastSourcePath = fromMmpToProjectPath(lastSourcePathStatement.getArgument()); + } else if (stmt.getSourcePathDependentContext() != null) { + // see if this statement can use the last sourcepath + IASTMMPSingleArgumentStatement reqdSourcePathStmt = + stmt.getSourcePathDependentContext().getSourcePathStatement(); + IPath reqdSourcePath = lastSourcePath; + + if (reqdSourcePathStmt != null) { + if (reqdSourcePathStmt == IMMPSourcePathDependentContext.DEFAULT_SOURCEPATH_STATEMENT) + reqdSourcePath = defaultProjectRelativeSourcePath(); + else + reqdSourcePath = fromMmpToProjectPath(reqdSourcePathStmt.getArgument()); + } + + if (!equalPath(lastSourcePath, reqdSourcePath)) { + // we're working only inside one TU at the moment, so + // it's fine to assume that equal IPaths means equal text, + // so we can copy the actual node originally referenced + iter.previous(); + if (reqdSourcePathStmt != null) { + lastSourcePathStatement = (IASTMMPSingleArgumentStatement) reqdSourcePathStmt.copy(); + } else { + lastSourcePathStatement = ASTMMPFactory.createMMPSingleArgumentStatement( + EMMPStatement.SOURCEPATH.toString(), + pathString(fromProjectToMmpPath(reqdSourcePath))); + } + iter.add(lastSourcePathStatement); + iter.next(); + lastSourcePath = reqdSourcePath; + } + } + } + + } + + @Override + protected void internalFinalizePreparserTranslationUnit(IASTTranslationUnit tu) { + addMissingSourcePaths(tu); + cleanupSourcePaths(tu); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.ViewBase#internalHasChanges() + */ + @Override + protected boolean internalHasChanges() { + return true; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.ViewBase#internalMerge(com.nokia.carbide.internal.api.cpp.epoc.engine.dom.IASTTranslationUnit) + */ + @Override + protected boolean internalMerge(IASTTranslationUnit oldTu) { + return false; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#createMMPBitmap() + */ + public IMMPBitmap createMMPBitmap() { + return new MMPBitmap(); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#createMMPAIFInfo() + */ + public IMMPAIFInfo createMMPAIFInfo() { + return new MMPAIFInfo(); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#createMMPResource() + */ + public IMMPResource createMMPResource() { + return new MMPResource(); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getASSPLibraries() + */ + public List getASSPLibraries() { + return stringListArgumentSettings.get(EMMPStatement.ASSPLIBRARY); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getAifInfo() + */ + public List getAifs() { + return aifs; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getMultiImageSources() + */ + public List getMultiImageSources() { + return (List) bitmaps; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getBitmaps() + */ + public List getBitmaps() { + return bitmaps; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getDebugLibraries() + */ + public List getDebugLibraries() { + return stringListArgumentSettings.get(EMMPStatement.DEBUGLIBRARY); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getFlags() + */ + public Set getFlags() { + return new ValidatingSet(flagSettings) { + + public boolean isValidEntry(EMMPStatement entry) { + return isFlagSettingStatement(entry); + } + + }; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getLanguages() + */ + public List getLanguages() { + return languages; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getLibraries() + */ + public List getLibraries() { + return stringListArgumentSettings.get(EMMPStatement.LIBRARY); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getListArgumentSettings() + */ + public Map> getListArgumentSettings() { + return new ValidatingKeyMap>(stringListArgumentSettings) { + + @Override + public boolean isAllowedKey(EMMPStatement key) { + return isStringListArgumentSettingStatement(key); + } + + }; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getMMPModel() + */ + public IMMPModel getMMPModel() { + return (IMMPModel) getModel(); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getResources() + */ + public List getResourceBlocks() { + return resourceBlocks; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getSingleArgumentSettings() + */ + public Map getSingleArgumentSettings() { + return new ValidatingKeyMap(singleArgumentSettings) { + + @Override + public boolean isAllowedKey(EMMPStatement key) { + return isSingleArgumentSettingStatement(key); + } + + }; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getEffectiveSourcePaths() + */ + public IPath[] getEffectiveSourcePaths() { + Set sourcePaths = new LinkedHashSet(); + for (IPath source : sources) { + sourcePaths.add(source.removeLastSegments(1)); + } + for (IPath source : userResources) { + sourcePaths.add(source.removeLastSegments(1)); + } + for (IPath source : systemResources) { + sourcePaths.add(source.removeLastSegments(1)); + } + for (IPath source : documents) { + sourcePaths.add(source.removeLastSegments(1)); + } + return (IPath[]) sourcePaths.toArray(new IPath[sourcePaths.size()]); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getRealSourcePaths() + */ + public IPath[] getRealSourcePaths() { + return (IPath[]) sourcePaths.toArray(new IPath[sourcePaths.size()]); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getSources() + */ + public List getSources() { + return sources; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getStaticLibraries() + */ + public List getStaticLibraries() { + return stringListArgumentSettings.get(EMMPStatement.STATICLIBRARY); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getSystemIncludes() + */ + public List getSystemIncludes() { + return systemIncludes; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getSystemResources() + */ + public List getSystemResources() { + return systemResources; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getTargetFilePath() + */ + public IPath getTargetFilePath() { + String name = singleArgumentSettings.get(EMMPStatement.TARGET); + if (name == null) + return null; + String dir = singleArgumentSettings.get(EMMPStatement.TARGETPATH); + if (dir == null) + return null; + IPath path = new Path(FileUtils.createPossiblyRelativePath(dir).append(name).toOSString()); + if (FileUtils.isPathInParent(path)) { + path = getProjectPath().append(path); + } + return path; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getTargetType() + */ + public String getTargetType() { + return singleArgumentSettings.get(EMMPStatement.TARGETTYPE); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getUserIncludes() + */ + public List getUserIncludes() { + return userIncludes; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getUserResources() + */ + public List getUserResources() { + return userResources; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getStaticLibraries() + */ + public List getWin32Libraries() { + return stringListArgumentSettings.get(EMMPStatement.WIN32_LIBRARY); + } + + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setAifInfo(com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPAIFInfo) + */ + public void setAifs(List aifs) { + Check.checkArg(aifs); + this.aifs = aifs; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setBitmaps(java.util.List) + */ + public void setBitmaps(List bitmaps) { + Check.checkArg(bitmaps); + this.bitmaps = bitmaps; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setTargetFilePath(org.eclipse.core.runtime.IPath) + */ + public void setTargetFilePath(IPath path) { + path = path.removeTrailingSeparator(); + Check.checkArg(path.segmentCount() > 1); + String filename = path.lastSegment(); + IPath dir = path.removeLastSegments(1); + singleArgumentSettings.put(EMMPStatement.TARGETPATH, pathString(dir)); + singleArgumentSettings.put(EMMPStatement.TARGET, filename); + + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getUid2() + */ + public String getUid2() { + return uid2; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getUid3() + */ + public String getUid3() { + return uid3; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setUid2(java.lang.String) + */ + public void setUid2(String uid) { + this.uid2 = uid; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setUid2(int) + */ + public void setUid2(int value) { + this.uid2 = "0x" + Integer.toHexString(value); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setUid3(java.lang.String) + */ + public void setUid3(String uid) { + this.uid3 = uid; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setUid3(int) + */ + public void setUid3(int value) { + this.uid3 = "0x" + Integer.toHexString(value); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getLinkerOptions() + */ + public Map getLinkerOptions() { + return linkerOptions; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getOptions() + */ + public Map getOptions() { + return options; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getReplaceOptions() + */ + public Map getReplaceOptions() { + return replaceOptions; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setLinkerOptions(java.util.Map) + */ + public void setLinkerOptions(Map options) { + Check.checkArg(options); + this.linkerOptions = options; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setOptions(java.util.Map) + */ + public void setOptions(Map options) { + Check.checkArg(options); + this.options = options; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#setReplaceOptions(java.util.Map) + */ + public void setReplaceOptions(Map options) { + Check.checkArg(options); + this.replaceOptions = options; + } + + /** + * Tell if the given macro is defined in the view configuration. + * @param macro + * @return + */ + public boolean isMacroDefined(String macro) { + for (IDefine define : getViewConfiguration().getMacros()) { + if (define.getName().equals(macro)) + return true; + } + return false; + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getDocuments() + */ + public List getDocuments() { + return documents; + } + + /** + * Track the statements known to the view from scanning. + * These will be updated when the view is updated. Any others + * (i.e. illegal statements or unhandled ones) will be left alone. + * @return + */ + public Collection getKnownStatements() { + return knownStatements; + } + + public IPath getDefFile() { + IMMPViewConfiguration viewConfiguration = (IMMPViewConfiguration) getViewConfiguration(); + String theDefFile = singleArgumentSettings.get(EMMPStatement.DEFFILE);; + return convertMMPDefFileToProjectOrFullPath( + theDefFile, isDefFileInFixedDirectory(), + viewConfiguration.getDefaultDefFileBase(isASSP()), + viewConfiguration.isEmulatorBuild()); + } + + public boolean isDefFileInFixedDirectory() { + String theDefFile = singleArgumentSettings.get(EMMPStatement.DEFFILE); + if (theDefFile == null) + return false; + return theDefFile.indexOf('/') >= 0 || theDefFile.indexOf('\\') >= 0; + } + + public void setDefFile(IPath path, boolean isFixedDirectory) { + // NOTE: we assume the new/modified statement always goes to the main file, otherwise postpone this + //Check.checkState(ModelSynchronizer.FORCE_NEW_CONTENT_TO_MAIN_DOCUMENT); + IMMPViewConfiguration viewConfiguration = (IMMPViewConfiguration) getViewConfiguration(); + String newDefFile = convertPhysicalFileToMMPDefFile(path, + viewConfiguration.getDefaultDefFileBase(isASSP()), + viewConfiguration.isEmulatorBuild(), + isFixedDirectory); + singleArgumentSettings.put(EMMPStatement.DEFFILE, newDefFile); + defFileBase = convertModelToProjectPath(new Path("")); // //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.ViewBase#addViewSpecificMessages(java.util.List) + */ + @Override + protected void addViewSpecificMessages(final List messageList) { + // also add IASTMMPUnknownStatements as warnings + getFilteredTranslationUnit().accept(new IASTVisitor() { + + public int visit(IASTNode node) { + if (node instanceof IASTMMPUnknownStatement) { + String text = node.getOriginalText(); + if (text == null) + text = node.getNewText(); + messageList.add(ASTMMPFactory.createMessage( + IMessage.WARNING, + "MMPView.UnknownStatement", //$NON-NLS-1$ + new Object[] { text }, + node.getMessageLocation())); + return IASTVisitor.VISIT_SIBLINGS; + } else if (node instanceof IASTMMPStatement + && EMMPStatement.MESSAGE.matches((IASTMMPStatement) node)) { + IASTListNode message = ((IASTMMPListArgumentStatement) node).getList(); + String text = message.getOriginalText(); + if (text == null) + text = message.getNewText(); + messageList.add(ASTMMPFactory.createMessage( + IMessage.WARNING, + "MMPView.MessageStatement", //$NON-NLS-1$ + new Object[] { text }, + node.getMessageLocation())); + return IASTVisitor.VISIT_SIBLINGS; + } + return IASTVisitor.VISIT_CHILDREN; + } + + }); + + } + + /* (non-Javadoc) + * @see com.nokia.carbide.internal.cpp.epoc.engine.model.ViewBase#getSpacingCategory(com.nokia.carbide.internal.api.cpp.epoc.engine.dom.IASTTopLevelNode) + */ + @Override + protected Object getSpacingCategory(IASTTopLevelNode node) { + if (!(node instanceof IASTMMPStatement) || (node instanceof IASTProblemNode)) + return null; + try { + EMMPStatement stmt = EMMPStatement.valueOf( + ((IASTMMPStatement) node).getKeywordName().toUpperCase()); + if (stmt == EMMPStatement.SOURCEPATH + || stmt == EMMPStatement.SOURCE + || stmt == EMMPStatement.RESOURCE + || stmt == EMMPStatement.SYSTEMRESOURCE + || stmt == EMMPStatement.DOCUMENT) { + return EMMPStatement.SOURCEPATH.getCategory(); + } + return stmt.getCategory(); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Merge a list of statements, with the assumption that only one instance + * of a given statement is retained. + * @param intoStmts original statements + * @param fromStmts ones to replace + */ + public void mergeStatementList(IASTListNode intoStmts, IASTListNode fromStmts) { + // unparent the fromStmts + for (IASTMMPStatement stmt : fromStmts) + stmt.setParent(null); + + for (ListIterator iter = intoStmts.listIterator(); iter.hasNext(); ) { + IASTMMPStatement into = iter.next(); + if (into instanceof IASTMMPProblemStatement) + continue; + + EMMPStatement stmt = null; + try { + stmt = EMMPStatement.valueOf(into.getKeywordName().toUpperCase()); + } catch (IllegalArgumentException e) { + // ignore + continue; + } + + boolean handled = false; + for (ListIterator iter2 = fromStmts.listIterator(); iter2.hasNext(); ) { + IASTMMPStatement from = iter2.next(); + try { + EMMPStatement stmt2 = EMMPStatement.valueOf(from.getKeywordName().toUpperCase()); + if (stmt == stmt2) { + // replace + iter.remove(); + iter.add(from); + iter2.remove(); + handled = true; + break; + } + } catch (IllegalArgumentException e2) { + // who knows? + } + } + + if (!handled) { + // the old statement is not replaced, so it must be removed + iter.remove(); + } + } + + // now, take remaining fromStmts and add + intoStmts.addAll(fromStmts); + } + + /** Helper method for now. Returns list of unique source paths. */ + Collection getSourcePaths() { + return sourcePaths; + } + + /** + * Convert the DEFFILE argument returned from IMMPView to a project-relative + * or absolute path. If the path has no directory or no filename, + * 'U' is added (for Unicode builds) and a platform-specific directory + * ("bwins" or "eabi") is added as in makmake. + * @param defFileSetting the DEFFILE setting from {@link IMMPView#getSingleArgumentSettings().get(EMMPStatement.DEFFILE)} -- may be null + * to determine default filename + * @param fixedDirectory + * @return a path derived from defFileSetting adjusted to the location of a .def file within a project + * or a full path if outside the project, or null. + */ + private IPath convertMMPDefFileToProjectOrFullPath(String defFileSetting, boolean fixedDirectory, String implDirectory, boolean isEmulator) { + String ext = null; + String baseName = null; + IPath dirPath = null; + + // no def file if not required + if (defFileSetting == null && !requiresDefFile()) + return null; + + IPath mmpPath = defFileSetting != null ? FileUtils.createPossiblyRelativePath(defFileSetting) : null; + + if (mmpPath != null && !mmpPath.hasTrailingSeparator()) { + // filename is provided + ext = mmpPath.getFileExtension(); + if (ext == null) + ext = DEF_FILE_EXTENSION; + IPath basePath = mmpPath.removeFileExtension(); + baseName = basePath.lastSegment(); + + if (basePath.isAbsolute()) { + dirPath = basePath.removeLastSegments(1); + } else { + dirPath = defFileBase.append(basePath.removeLastSegments(1)); + } + //if (!dirPath.isAbsolute()) + // dirPath = ((IModel)mmpView.getModel()).getPath().removeLastSegments(1).append(dirPath); + + } else { + // no filename, so guess it from the target name + //if (mmpPath == null || !mmpPath.isAbsolute()) + // dirPath = ((IModel)mmpView.getModel()).getPath().removeLastSegments(1).append(mmpPath); + //else + if (mmpPath == null) + dirPath = new Path(""); //$NON-NLS-1$ + else + dirPath = mmpPath; + dirPath = convertModelToProjectPath(dirPath); + String targetName = getSingleArgumentSettings().get(EMMPStatement.TARGET); + if (targetName == null) + targetName = "unnamed"; //$NON-NLS-1$ + baseName = new Path(targetName).removeFileExtension().lastSegment(); + ext = DEF_FILE_EXTENSION; + } + + // add implicit directory if needed + if (!fixedDirectory) { + if (implDirectory != null) { + dirPath = dirPath.append("..").append(implDirectory); //$NON-NLS-1$ + } + } else { + // replace any "/~/" sequences + if (dirPath.segmentCount() > 0) + dirPath = new Path(dirPath.addTrailingSeparator().toString().replace("/~/", "/"+implDirectory+"/")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + // version identifier overrides unicode + String versionString = getFileVersionString(); + if (!isEmulator && versionString != null) { + baseName += versionString; + } + else if (!isNoStrictDef()) { + // unicode is on by default unless the user wants to explicitly set the name + baseName += "U"; //$NON-NLS-1$ + } + + IPath prjPath = null; + IPath tempPath = dirPath.append(baseName + "." + ext); //$NON-NLS-1$ + if (!tempPath.isAbsolute()) { + prjPath = tempPath; //convertMMPToProject(EMMPPathContext.DEFFILE, tempPath); + } else if (mmpPath == null || !mmpPath.isAbsolute()) { + /* + IPath wsPath = epocHelper.convertFilesystemToWorkspace(tempPath); + if (wsPath != null) + prjPath = wsPath.removeFirstSegments(1); + + if (prjPath == null) { + if (mmpView != null) { + IPath projectRoot = mmpView.getViewConfiguration().getViewParserConfiguration().getProjectLocation(); + prjPath = FileUtils.removePrefixFromPath(projectRoot, tempPath); + } + } + */ + } + if (prjPath == null) { + // e.g. for full paths + prjPath = tempPath; + } + return prjPath; + } + + private String getFileVersionString() { + List versionArgs = getListArgumentSettings().get(EMMPStatement.VERSION); + if (versionArgs == null || versionArgs.size() < 1) + return null; + try { + Version version = new Version(versionArgs.get(0)); + Formatter formatter = new Formatter(); + formatter.format("{%04x%04x}", version.getMajor(), version.getMinor()); //$NON-NLS-1$ + return formatter.toString(); + } catch (IllegalArgumentException e) { + return null; + } + } + + + /** + * Convert a def file path, which may have a 'U' added for a Unicode build, + * and an implicit bwins or eabi directory, and remove them. This may + * return null if the DEFFILE statement is not needed. + *

+ * This does not provide a complete mapping back to the appropriate DEFFILE + * statement if the realPath was previously created from {@link #convertMMPDefFileToProjectOrFullPath(String)} + * where the DEFFILE statement was missing. The client should determine whether + * a non-null result is sensible (e.g. points to a real file). + *

+ * @param realPath the path to a real file; if relative, assumed project-relative, else assumed full path + * @param implDirectory the directory for platform .def files or null for unknown + * @param isEmulator true if an emulator build, else false: determines whether the version is added + * @param isFixedDirectory true if the realPath should not be converted to platform-independent + * @return a string adjusted to the MMP format. It may be null. + */ + private String convertPhysicalFileToMMPDefFile(IPath realPath, String implDirectory, boolean isEmulator, boolean isFixedDirectory) { + if (realPath == null) + return null; + + if (!realPath.isAbsolute()) { + //realPath = ((IModel)mmpView.getModel()).getPath().removeLastSegments(1).append(realPath); + realPath = convertProjectToModelPath(realPath); + } + + String ext = null; + String baseName = null; + IPath dirPath = null; + + ext = FileUtils.getSafeFileExtension(realPath); + IPath basePath = realPath.removeFileExtension(); + baseName = basePath.lastSegment(); + dirPath = basePath.removeLastSegments(1); + + // remove Unicode or version suffix + if (!isNoStrictDef() && baseName.toUpperCase().endsWith("U")) { //$NON-NLS-1$ + baseName = baseName.substring(0, baseName.length() - 1); + } else { + Matcher matcher = VERSION_PATTERN.matcher(baseName); + if (matcher.matches()) { + baseName = matcher.group(1); + } + } + + // remove implicit directory if present + if (!isFixedDirectory && implDirectory != null && dirPath.segmentCount() > 0 && dirPath.lastSegment().equalsIgnoreCase(implDirectory)) { + dirPath = dirPath.removeLastSegments(2); // ".." and impl + } + + // return string value, containing literal .\ if not generic name + String mmpPath = pathString(dirPath.append(baseName + "." + ext)); //$NON-NLS-1$ + if (isFixedDirectory && !dirPath.isAbsolute() && dirPath.segmentCount() == 0) + mmpPath = "." + pathSeparator() + mmpPath; //$NON-NLS-1$ + return mmpPath; + } + + /** + * Tell whether NOSTRICTDEF was defined in the MMP view, e.g., + * if the view does not change the name of DEFFILEs for Unicode + * builds. + * @return flag + */ + private boolean isNoStrictDef() { + return getFlags().contains(EMMPStatement.NOSTRICTDEF); + } + + /** + * Tell whether the ASSP format exports are used + * @return flag + */ + private boolean isASSP() { + return getFlags().contains(EMMPStatement.ASSPEXPORTS); + } + + /** + * Tell whether the MMP file requires a .def file, which tells whether + * a missing DEFFILE statement has an implicit one. + * @return flag + */ + private boolean requiresDefFile() { + String targetType = getSingleArgumentSettings().get(EMMPStatement.TARGETTYPE); + if (targetType == null) + return false; + return targetType.toUpperCase().matches("DLL|EXEDLL|EXEXP|IMPLIB|STDDLL"); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPView#getData() + */ + public IMMPData getData() { + return new MMPData(this); + } + + /* (non-Javadoc) + * @see com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPData#getModelPath() + */ + public IPath getModelPath() { + return model.getPath(); + } +}