/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.mxml; import java.util.Collection; import java.util.ListIterator; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.common.PrefixMap; import org.apache.flex.compiler.common.SourceLocation; import org.apache.flex.compiler.common.XMLName; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.parsing.ISourceFragment; import org.apache.flex.compiler.internal.parsing.SourceFragment; import org.apache.flex.compiler.internal.parsing.mxml.MXMLToken; import org.apache.flex.compiler.mxml.IMXMLTagAttributeData; import org.apache.flex.compiler.mxml.IMXMLTagData; import org.apache.flex.compiler.parsing.MXMLTokenTypes; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.MXMLUnclosedTagProblem; import org.apache.flex.compiler.problems.SyntaxProblem; /** * Represents a tag attribute in MXML. */ public class MXMLTagAttributeData extends SourceLocation implements IMXMLTagAttributeData { /** * Constructor. * <p> * Each attribute consumes three tokens: * {@code TOKEN_NAME}, {@code TOKEN_EQUALS}, and {@code TOKEN_STRING}. */ MXMLTagAttributeData(MXMLToken nameToken, ListIterator<MXMLToken> tokenIterator, MXMLDialect mxmlDialect, IFileSpecification spec, Collection<ICompilerProblem> problems) { setStart(nameToken.getStart()); setLine(nameToken.getLine()); setColumn(nameToken.getColumn()); setEndLine(nameToken.getEndLine()); setEndColumn(nameToken.getEndColumn()); setEnd(nameToken.getEnd()); // Deal with name if it is of the form name.state MXMLStateSplitter splitState = new MXMLStateSplitter(nameToken, mxmlDialect, problems, spec); attributeName = splitState.getBaseName(); if (splitState.getStateName() != null) { stateName = splitState.getStateName(); stateStart = nameToken.getStart() + splitState.getStateNameOffset(); } MXMLToken token = null; // Look for "=" token. if (tokenIterator.hasNext()) { token = tokenIterator.next(); if (token.getType() != MXMLTokenTypes.TOKEN_EQUALS) { if (token.getSourcePath() == null) token.setSourcePath(spec.getPath()); problems.add(new SyntaxProblem(token)); // Restore the token position for error recovery. tokenIterator.previous(); return; } valueStart = token.getEnd(); // set the value's start to right after the equals until we have a value valueLine = token.getLine(); valueColumn = token.getColumn(); } Boolean firstToken = true; // Look for value token. while (tokenIterator.hasNext()) { token = tokenIterator.next(); if (token.getType() == MXMLTokenTypes.TOKEN_STRING) { valueIncludingDelimiters = token.getText(); } else { if (firstToken) { if (token.getSourcePath() == null) token.setSourcePath(spec.getPath()); problems.add(new SyntaxProblem(token)); } else if (!MXMLToken.isTagEnd(token.getType()) && token.getType() != MXMLTokenTypes.TOKEN_NAME) { if (token.getSourcePath() == null) token.setSourcePath(spec.getPath()); problems.add(new MXMLUnclosedTagProblem(token)); } // Restore the token position for error recovery. tokenIterator.previous(); return; } firstToken = false; } } /** * The MXML tag that contains this attribute. */ protected IMXMLTagData parent; /** * The URI specified by this attribute's prefix. */ protected String uri; /** * The name of this attribute. */ protected String attributeName; /** * The name of this state, if it exists. */ protected String stateName; /** * The offset at which the optional state starts. */ protected int stateStart; /** * The attribute value, including any delimiters. */ protected String valueIncludingDelimiters; /** * The offset at which the attribute value starts. */ protected int valueStart; /** * The line on which the attribute value starts. */ protected int valueLine; /** * The column at which the attribute value starts. */ protected int valueColumn; // // Object overrides. // // For debugging only. This format is nice in the Eclipse debugger. @Override public String toString() { StringBuilder sb = new StringBuilder(); buildAttributeString(false); return sb.toString(); } // // IMXMLTagAttributeData implementations // @Override public MXMLDialect getMXMLDialect() { return getParent().getParent().getMXMLDialect(); } @Override public IMXMLTagData getParent() { return parent; } @Override public String getName() { return attributeName; } @Override public String getPrefix() { String name = getName(); int i = name.indexOf(':'); return i != -1 ? name.substring(0, i) : null; } @Override public String getURI() { if (uri == null) { //walk up our chain to find the correct uri for our namespace. first one wins String prefix = getPrefix(); if (prefix == null) return null; IMXMLTagData lookingAt = parent; // For attributes with prefix, parent's parent can be null if // parent is the root tag while (lookingAt != null && lookingAt.getParent() != null) { PrefixMap depth = lookingAt.getParent().getPrefixMapForData(lookingAt); if (depth != null && depth.containsPrefix(prefix)) { uri = depth.getNamespaceForPrefix(prefix); break; } lookingAt = lookingAt.getParentTag(); } } return uri; } @Override public String getShortName() { String name = getName(); int i = name.indexOf(':'); return i != -1 ? name.substring(i + 1) : name; } @Override public XMLName getXMLName() { return new XMLName(getURI(), getShortName()); } @Override public String getStateName() { return stateName != null ? stateName : ""; } @Override public boolean isSpecialAttribute(String name) { String languageURI = getMXMLDialect().getLanguageNamespace(); return getName().equals(name) && (getPrefix() == null || getURI() == languageURI); } @Override public boolean hasValue() { return getRawValue() != null; } @Override public String getRawValue() { String value = getValueWithQuotes(); if (value != null && value.length() > 0) { // length can be one in case of invalid data and then the substring() call fails // so, handle it here if (value.charAt(0) == value.charAt(value.length() - 1) && value.length() != 1) value = value.substring(1, value.length() - 1); else value = value.substring(1); } return value; } @Override public ISourceFragment[] getValueFragments(Collection<ICompilerProblem> problems) { String value = getRawValue(); if (value == null) return new ISourceFragment[0]; ISourceLocation location = getValueLocation(); MXMLDialect mxmlDialect = getMXMLDialect(); String s = EntityProcessor.parseAsString(value, location, mxmlDialect, problems); ISourceFragment[] result = new ISourceFragment[1]; result[0] = new SourceFragment(s, location); return result; } @Override public int getValueStart() { return hasValue() ? valueStart + 1 : 0; } @Override public int getValueEnd() { if (hasValue()) return getValueStart() + getRawValue().length(); // If there is no valid "end", then we must return -1. Callers depend on this. // See MXMLTagData.findArttributeContainingOffset for an example return -1; } @Override public int getValueLine() { return hasValue() ? valueLine : 0; } @Override public int getValueColumn() { return hasValue() ? valueColumn + 1 : 0; } @Override public SourceLocation getValueLocation() { return new SourceLocation(getSourcePath(), getValueStart(), getValueEnd(), getValueLine(), getValueColumn()); } // // Other methods // public IFileSpecification getSource() { return getParent().getSource(); } /** * Sets this attribute's tag. * * @param parent MXML tag containing this attribute */ public void setParent(IMXMLTagData parent) { this.parent = parent; setSourcePath(parent.getSourcePath()); } /** * Adjust all associated offsets by the adjustment amount * * @param offsetAdjustment amount to add to offsets */ public void adjustOffsets(int offsetAdjustment) { if (attributeName != null) { setStart(getAbsoluteStart() + offsetAdjustment); setEnd(getAbsoluteEnd() + offsetAdjustment); } if (hasValue()) valueStart += offsetAdjustment; if (stateName != null) stateStart += offsetAdjustment; } /** * Returns the {@link PrefixMap} that represents all prefix->namespace * mappings are in play on this tag. For example, if a parent tag defines * <code>xmlns:m="falcon"</code> and this tag defines * <code>xmlns:m="eagle"</code> then in this prefix map, m will equal * "eagle" * * @return a {@link PrefixMap} or null */ public PrefixMap getCompositePrefixMap() { return parent.getCompositePrefixMap(); } void invalidateURI() { uri = null; } /** * Gets the starting offset for this attribute's name. */ public int getNameStart() { return getAbsoluteStart(); } /** * Gets the ending offset for this attribute's name. */ public int getNameEnd() { return getAbsoluteStart() + attributeName.length(); } /** * Gets the line number for this attribute's name. * * @return end offset */ public final int getNameLine() { return getLine(); } /** * Get the column number for this attribute's name. * * @return end offset */ public final int getNameColumn() { return getColumn(); } /** * Does the offset fall inside the bounds of the attribute name? * * @param offset test offset * @return true if the offset falls within the attribute name */ public boolean isInsideName(int offset) { if (attributeName != null) return MXMLData.contains(getNameStart(), getNameEnd(), offset); return false; } /** * Checks whether this attribute is associated with a state. * * @return True if a state association exists. */ public boolean hasState() { return stateName != null; } /** * Get this attribute's state start offset if a state token is present other * wise zero. * * @return state start offset or zero */ public int getStateStart() { return stateName != null ? stateStart : 0; } /** * Get this attribute's state tokens end offset if a state token is present * other wise zero. * * @return state start offset or zero */ public int getStateEnd() { return stateName != null ? stateStart + stateName.length() : 0; } public boolean isInsideStateName(int offset) { if (stateName != null) return MXMLData.contains(getStateStart(), getStateEnd(), offset); return false; } /** * Gets the attribute value as a String (with quotes). * * @return attribute value (with quotes) */ public String getValueWithQuotes() { return valueIncludingDelimiters; } /** * Does this value have a closing quote character? * * @return true if this value has a closing quote character */ protected boolean valueIsWellFormed() { // If there is a value, it came from a string token. We know (from the // RawTagTokenizer) that this means it starts with a quote character. If // it ends with the same quote character, it's well formed. if (hasValue()) { char firstChar = valueIncludingDelimiters.charAt(0); char lastChar = valueIncludingDelimiters.charAt(valueIncludingDelimiters.length() - 1); return (firstChar == '"' || firstChar == '\'') && firstChar == lastChar; } return false; } /** * Does the offset fall inside the bounds of the attribute value? * * @param offset test offset * @return true if the offset falls within the attribute value */ public boolean isInsideValue(int offset) { if (hasValue()) return MXMLData.contains(getValueStart() - 1, getValueEnd(), offset); return false; } /** * For unit tests only. * * @return name value and offsets in string form */ public String buildAttributeString(boolean skipSrcPath) { StringBuilder sb = new StringBuilder(); sb.append(getName()); sb.append('='); sb.append('"'); sb.append(getRawValue()); sb.append('"'); sb.append(' '); // Display line, column, start, and end as "17:5 160-188". if (skipSrcPath) sb.append(getOffsetsString()); else sb.append(super.toString()); return sb.toString(); } /** * Verifies that this attribute has its source location information set. * <p> * This is used only in asserts. */ public boolean verify() { // Verify the source location. assert getSourcePath() != null : "Attribute has null source path: " + toString(); assert getStart() != UNKNOWN : "Attribute has unknown start: " + toString(); assert getEnd() != UNKNOWN : "Attribute has unknown end: " + toString(); assert getLine() != UNKNOWN : "Attribute has unknown line: " + toString(); assert getColumn() != UNKNOWN : "Attribute has unknown column: " + toString(); return true; } }