001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019:
020: package org.netbeans.modules.xml.xpath.ext;
021:
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.LinkedList;
025: import java.util.List;
026: import java.util.Set;
027: import org.netbeans.modules.xml.schema.model.Attribute;
028: import org.netbeans.modules.xml.schema.model.SchemaComponent;
029: import org.netbeans.modules.xml.xam.Named;
030:
031: /**
032: * It is intended to:
033: * -- resolve schema types of steps in a location path.
034: * -- specify a context for relative locatoin paths;
035: * -- specify a context for the root step of the absolute locatoin paths;
036: * -- specify a context for XSL templates or for-each constructs.
037: *
038: * Contexts can be organized in chains. This chains can contain repeated
039: * parts in case of recursive XML schemas.
040: *
041: * Context can reference multiple Schema components. It is necessary to support
042: * XPath wildcards "*" or double slash "//". In such case a location step can
043: * have a set of possible schema types.
044: *
045: * The context isn't intended to specify a global schema element (type) from
046: * which an absolute location path should be started.
047: *
048: * It is also isn't intended to specify a schema type, which should be produced
049: * as a result of an XPath expression usage.
050: *
051: * The main use-case is the following:
052: * -- The new context is constructed or taken from outside and it is specified
053: * for a new XPath model.
054: * -- The Model is parsed and resolved. The internal model schema resolver
055: * assigns context for all components of the XPath model, for which
056: * it can be specified.
057: *
058: * @author nk160297
059: */
060: public interface XPathSchemaContext {
061:
062: /**
063: * Refers to the parent context.
064: */
065: XPathSchemaContext getParentContext();
066:
067: /**
068: * Returns objects which hold the references to context schema components.
069: */
070: Set<SchemaCompPair> getSchemaCompPairs();
071:
072: /**
073: * This method returns only those schema component pairs which
074: * are used by next element of the context chain.
075: */
076: Set<SchemaCompPair> getUsedSchemaCompPairs();
077:
078: /**
079: * The context can contain multiple variants of Schema component.
080: * But if it isn't the last in the chain then there are next chain item
081: * which can specify which schema components are used.
082: */
083: void setUsedSchemaComp(Set<SchemaComponent> compSet);
084:
085: /**
086: * Compare this and parents' chain context
087: * @param obj
088: * @return
089: */
090: boolean equalsChain(XPathSchemaContext obj);
091:
092: /**
093: * This class contans current and parent schema components.
094: * It keeps track from which parent schema component the current
095: * component was taken from.
096: */
097: public final class SchemaCompPair {
098: private SchemaComponent mComp;
099: private SchemaComponent mParentComp;
100:
101: public SchemaCompPair(SchemaComponent comp,
102: SchemaComponent parent) {
103: mComp = comp;
104: mParentComp = parent;
105: }
106:
107: public SchemaComponent getComp() {
108: return mComp;
109: }
110:
111: public SchemaComponent getParetnComp() {
112: return mParentComp;
113: }
114:
115: @Override
116: public String toString() {
117: StringBuffer sb = new StringBuffer();
118: //
119: SchemaComponent parentComp = getParetnComp();
120: if (parentComp != null) {
121: SchemaCompPair.appendCompName(sb, parentComp);
122: sb.append(">");
123: }
124: SchemaComponent schemaComp = getComp();
125: SchemaCompPair.appendCompName(sb, schemaComp);
126: //
127: return sb.toString();
128: }
129:
130: @Override
131: public boolean equals(Object obj) {
132: if (obj != null && obj instanceof SchemaCompPair) {
133: SchemaCompPair other = (SchemaCompPair) obj;
134: return (other.mComp == mComp)
135: && (other.mParentComp == mParentComp);
136: }
137: //
138: return false;
139: }
140:
141: /**
142: * Helper method for toString
143: */
144: public static void appendCompName(StringBuffer sb,
145: SchemaComponent schemaComp) {
146: if (schemaComp instanceof Attribute) {
147: sb.append("@");
148: }
149: if (schemaComp instanceof Named) {
150: String name = ((Named) schemaComp).getName();
151: sb.append(name);
152: } else {
153: sb.append("???"); // NOI18N
154: }
155: }
156:
157: }
158:
159: public final class Utilities {
160:
161: /**
162: * Returns a chain of schema components if there is the only one possible
163: * variant of it. Otherwise returns null.
164: * @param context
165: * @return
166: */
167: public static List<SchemaComponent> getSchemaCompChain(
168: XPathSchemaContext context) {
169: ArrayList<SchemaComponent> result = new ArrayList<SchemaComponent>();
170: //
171: do {
172: SchemaComponent sComp = getSchemaComp(context);
173: if (sComp == null) {
174: return null;
175: } else {
176: result.add(sComp);
177: context = context.getParentContext();
178: }
179: } while (context != null);
180: //
181: return result;
182: }
183:
184: /**
185: * Returns a schema component in case if there is only one possible
186: * variant. Otherwise returns null.
187: * @param context
188: * @return
189: */
190: public static SchemaComponent getSchemaComp(
191: XPathSchemaContext context) {
192: Set<SchemaCompPair> scPairSet = context
193: .getUsedSchemaCompPairs();
194: if (scPairSet != null && scPairSet.size() == 1) {
195: SchemaCompPair scPair = scPairSet.iterator().next();
196: if (scPair != null) {
197: SchemaComponent sComp = scPair.getComp();
198: return sComp;
199: }
200: }
201: //
202: return null;
203: }
204:
205: public static boolean equalsChain(XPathSchemaContext cont1,
206: XPathSchemaContext cont2) {
207: if (equals(cont1, cont2)) {
208: //
209: // Compare parent contexts
210: XPathSchemaContext parentCont1 = cont1
211: .getParentContext();
212: XPathSchemaContext parentCont2 = cont2
213: .getParentContext();
214: if (parentCont1 != null && parentCont2 != null) {
215: boolean result = equalsChain(parentCont1,
216: parentCont2);
217: if (!result) {
218: return false;
219: }
220: } else if ((parentCont1 == null && parentCont2 != null)
221: || (parentCont1 != null && parentCont2 == null)) {
222: return false;
223: }
224: //
225: return true;
226: }
227: //
228: return false;
229: }
230:
231: public static boolean equals(XPathSchemaContext cont1,
232: XPathSchemaContext cont2) {
233: assert cont1 != null && cont2 != null;
234: //
235: // Compare component pairs first
236: Set<SchemaCompPair> compPairSet1 = cont1
237: .getSchemaCompPairs();
238: Set<SchemaCompPair> compPairSet2 = cont2
239: .getSchemaCompPairs();
240: //
241: assert compPairSet1 != null && compPairSet2 != null;
242: //
243: if (compPairSet1.size() != compPairSet2.size()) {
244: return false;
245: }
246: //
247: Iterator<SchemaCompPair> scpItr1 = compPairSet1.iterator();
248: while (scpItr1.hasNext()) {
249: SchemaCompPair scp1 = scpItr1.next();
250: if (!compPairSet2.contains(scp1)) {
251: return false;
252: }
253: }
254: //
255: return true;
256: }
257:
258: /**
259: * Generates a new ralative location path by an absolute expression path
260: * and a schema context.
261: *
262: * @param absolutePath
263: * @param context
264: * @return
265: */
266: public static XPathLocationPath generateRelativePath(
267: XPathExpressionPath absolutePath,
268: XPathSchemaContext context) {
269: //
270: LocationStep[] originalSteps = absolutePath.getSteps();
271: if (originalSteps == null || originalSteps.length == 0) {
272: return null;
273: }
274: //
275: LocationStep lastStep = originalSteps[originalSteps.length - 1];
276: XPathSchemaContext lastStepContext = lastStep
277: .getSchemaContext();
278: if (lastStepContext == null) {
279: // The last step isn't properly resolved
280: // Impossible to calculate relative path.
281: return null;
282: }
283: //
284: // Obtain the schema context of the root expression
285: XPathExpression rootExpression = absolutePath
286: .getRootExpression();
287: XPathSchemaContext rootExprContext = null;
288: if (rootExpression instanceof XPathSchemaContextHolder) {
289: rootExprContext = ((XPathSchemaContextHolder) rootExpression)
290: .getSchemaContext();
291: }
292: if (rootExprContext == null) {
293: return null;
294: }
295: assert rootExprContext.getParentContext() == null : "the root expression has to have not chained schema context";
296: //
297: // Looking for the deepest common root
298: //
299: List<XPathSchemaContext> absPathContextsList = getInversedContextChain(lastStepContext);
300: List<XPathSchemaContext> contextsList = getInversedContextChain(context);
301: //
302: Iterator<XPathSchemaContext> absPathContextsItr = absPathContextsList
303: .iterator();
304: Iterator<XPathSchemaContext> contextsItr = contextsList
305: .iterator();
306: //
307: XPathSchemaContext deepestCommonContext = null;
308: int commonContextInd = -1;
309: //
310: while (absPathContextsItr.hasNext()
311: && contextsItr.hasNext()) {
312: XPathSchemaContext context1 = contextsItr.next();
313: XPathSchemaContext context2 = absPathContextsItr.next();
314: //
315: if (!context1.equals(context2)) {
316: break;
317: }
318: //
319: commonContextInd++;
320: deepestCommonContext = context1;
321: }
322: //
323: if (deepestCommonContext == null) {
324: return null;
325: }
326: if (deepestCommonContext.equals(rootExprContext)) {
327: // in this case the relative path doesn't matter
328: // because of the deepest common context is the same as
329: // the context of the root XPath expression.
330: return null;
331: }
332: //
333: // Construct the path
334: XPathModelFactory factory = lastStep.getModel()
335: .getFactory();
336: ArrayList<LocationStep> stepsList = new ArrayList<LocationStep>();
337: //
338: // Construct "Go To Parent" steps
339: // Count number of "Go To Parent" steps which is necessary to add.
340: int goToParentCount = contextsList.size()
341: - commonContextInd - 1;
342: XPathSchemaContext parentContext = context
343: .getParentContext();
344: for (int index = 0; index < goToParentCount; index++) {
345: LocationStep goToParent = factory.newLocationStep(
346: XPathAxis.PARENT, new StepNodeTypeTest(
347: StepNodeTestType.NODETYPE_NODE, null),
348: null);
349: goToParent.setSchemaContext(parentContext);
350: stepsList.add(goToParent);
351: }
352: //
353: // Copy a tail of of original location step's chain to the new relative path
354: // The originalSteps array doesn't contain the first schema context.
355: // It is taken from the root expression of the absolute path.
356: // So the index is reduced by 1.
357: for (int index = commonContextInd; index < originalSteps.length; index++) {
358: LocationStep originalStep = originalSteps[index];
359: stepsList.add(originalStep);
360: }
361: //
362: // Add SELF step (.) if there is not other steps
363: // and the last step of the absolute path has the same schema
364: // context as that which is used to calculate relative path.
365: // This check can be done at the beginning of the method
366: // but it is very rare case.
367: if (stepsList.size() == 0) {
368: if (lastStepContext.equalsChain(context)) {
369: LocationStep selfStep = factory.newLocationStep(
370: XPathAxis.SELF, new StepNodeTypeTest(
371: StepNodeTestType.NODETYPE_NODE,
372: null), null);
373: stepsList.add(selfStep);
374: }
375: }
376: //
377: XPathLocationPath result = factory
378: .newXPathLocationPath(stepsList
379: .toArray(new LocationStep[stepsList.size()]));
380: //
381: return result;
382: }
383:
384: /**
385: * Constructs the list of schema contexts based on the context chain.
386: * Thi list provides reverced access to the chain so the last element
387: * in the list is the element, which is specified as a paramenter of
388: * the method.
389: *
390: * @param context
391: * @return
392: */
393: public static List<XPathSchemaContext> getInversedContextChain(
394: XPathSchemaContext context) {
395: //
396: LinkedList<XPathSchemaContext> result = new LinkedList<XPathSchemaContext>();
397: while (context != null) {
398: result.addFirst(context);
399: context = context.getParentContext();
400: }
401: //
402: return result;
403: }
404:
405: }
406: }
|