001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.config.schema.defaults;
006:
007: import org.apache.xmlbeans.SchemaLocalAttribute;
008: import org.apache.xmlbeans.SchemaParticle;
009: import org.apache.xmlbeans.SchemaType;
010: import org.apache.xmlbeans.XmlException;
011: import org.apache.xmlbeans.XmlObject;
012:
013: import com.tc.util.Assert;
014:
015: import java.util.regex.Pattern;
016:
017: /**
018: * A {@link DefaultValueProvider} that gets defaults from the schema itself.
019: */
020: public class FromSchemaDefaultValueProvider implements
021: DefaultValueProvider {
022:
023: public FromSchemaDefaultValueProvider() {
024: // Nothing here.
025: }
026:
027: // This makes sure we don't try to interpret XPaths that have anything other than normal element delimiters in them.
028: private static final Pattern ALLOWED_COMPONENT_PATTERN = Pattern
029: .compile("@?[A-Za-z0-9][A-Za-z0-9-]*");
030:
031: public boolean isOptional(SchemaType baseType, String xpath)
032: throws XmlException {
033: return fetchParticle(baseType, xpath).isOptional();
034: }
035:
036: public boolean possibleForXPathToHaveDefault(String xpath) {
037: return isInterpretableXPath(xpath);
038: }
039:
040: public XmlObject defaultFor(SchemaType baseType, String xpath)
041: throws XmlException {
042: XmlObject out = fetchDefault(baseType, xpath);
043: if (out == null)
044: throw new XmlException("The element at XPath '" + xpath
045: + "' has no default specified.");
046: else
047: return out;
048: }
049:
050: public boolean hasDefault(SchemaType baseType, String xpath)
051: throws XmlException {
052: return fetchDefault(baseType, xpath) != null;
053: }
054:
055: private XmlObject fetchDefault(SchemaType baseType, String xpath)
056: throws XmlException {
057: SchemaInfo info = fetchParticle(baseType, xpath);
058: if (!info.isDefault())
059: return null;
060: else
061: return info.defaultValue();
062: }
063:
064: private static class SchemaInfo {
065: private final boolean isDefault;
066: private final boolean isOptional;
067: private final XmlObject defaultValue;
068:
069: public SchemaInfo(boolean isDefault, boolean isOptional,
070: XmlObject defaultValue) {
071: Assert.eval(defaultValue == null || isDefault);
072: Assert.eval(defaultValue == null || isOptional);
073:
074: this .isDefault = isDefault;
075: this .isOptional = isOptional;
076: this .defaultValue = defaultValue;
077: }
078:
079: public boolean isDefault() {
080: return this .isDefault;
081: }
082:
083: public boolean isOptional() {
084: return this .isOptional;
085: }
086:
087: public XmlObject defaultValue() {
088: return this .defaultValue;
089: }
090: }
091:
092: // This is the single most incomprehensible piece of code in the entire system. I have absolutely no idea what this
093: // XPath stuff is actually supposed to do; there is almost no documentation on it. I made this work by long
094: // trial-and-error. You're on your own here, mate.
095: private SchemaInfo fetchParticle(SchemaType baseType, String xpath)
096: throws XmlException {
097: Assert.assertNotNull(baseType);
098: Assert.assertNotBlank(xpath);
099:
100: if (!isInterpretableXPath(xpath)) {
101: // formatting
102: throw new XmlException(
103: "Right now, our default-finding code doesn't support anything other than a path consisting "
104: + "of only simple elements. '"
105: + xpath
106: + "' is not such a path.");
107: }
108:
109: String[] components = xpath.split("/");
110: SchemaParticle currentParticle = baseType.getContentModel();
111: Assert.assertNotNull(currentParticle);
112:
113: boolean anyAreOptional = false;
114:
115: if (components.length == 1 && components[0].startsWith("@")) {
116: // We don't yet support direct attribute grabs for defaults; this is because I can't figure out how to get
117: // XMLBeans to do them right.
118: return new SchemaInfo(false, currentParticle.getMinOccurs()
119: .intValue() == 0, null);
120: }
121:
122: for (int i = 0; i < components.length; ++i) {
123: String component = components[i];
124: if (currentParticle.getMinOccurs().intValue() == 0)
125: anyAreOptional = true;
126:
127: int particleType = currentParticle.getParticleType();
128:
129: // Attributes should've been caught on the last time through.
130: if (component.startsWith("@")) {
131: // formatting
132: throw new XmlException(
133: "Component '"
134: + component
135: + "' of XPath '"
136: + xpath
137: + "' specifies an attribute in an invalid position.");
138: }
139:
140: if ((i == components.length - 2)
141: && (components[i + 1].startsWith("@"))) {
142: String attributeName = components[i + 1].substring(1);
143:
144: if (currentParticle.getType() == null
145: || currentParticle.getType()
146: .getAttributeModel() == null) {
147: // formatting
148: throw new XmlException(
149: "The element purportedly containing attribute '"
150: + attributeName
151: + "' in XPath '"
152: + xpath
153: + "' seems to have no attributes at all.");
154: }
155:
156: SchemaLocalAttribute[] attributes = currentParticle
157: .getType().getAttributeModel().getAttributes();
158: for (int j = 0; j < attributes.length; ++j) {
159: if (attributes[j].getName().getLocalPart().equals(
160: attributeName)) {
161: return new SchemaInfo(
162: attributes[j].isDefault(),
163: attributes[j].getMinOccurs().intValue() == 0,
164: attributes[j].getDefaultValue());
165: }
166: }
167:
168: throw new XmlException("Attribute '" + attributeName
169: + "' of element '" + component + "' in XPath '"
170: + xpath + "' was not found.");
171: }
172:
173: if (particleType == SchemaParticle.ELEMENT) {
174: if (currentParticle.getName().getLocalPart().equals(
175: component)) {
176: if (i == components.length - 1)
177: break;
178: else {
179: currentParticle = currentParticle.getType()
180: .getContentModel();
181: Assert.assertNotNull(currentParticle);
182: continue;
183: }
184: } else {
185: throw new XmlException(
186: "Component '"
187: + component
188: + "' of XPath '"
189: + xpath
190: + "' not found; we have one element only, '"
191: + currentParticle.getName()
192: .getLocalPart() + "'.");
193: }
194: }
195:
196: checkParticleType(component, particleType, xpath);
197:
198: SchemaParticle[] children = currentParticle
199: .getParticleChildren();
200: if (children == null) {
201: // formatting
202: throw new XmlException("Component '" + component
203: + "' of XPath '" + xpath + "' seems to have "
204: + "no children. Stop.");
205: }
206:
207: ElementReturn elementReturn = findNextElement(xpath,
208: component, children, i == components.length - 1);
209: SchemaParticle next = elementReturn.particle();
210: anyAreOptional = anyAreOptional
211: || elementReturn.isOptional();
212:
213: if (next == null) {
214: // formatting
215: throw new XmlException("Component '" + component
216: + "' of XPath '" + xpath
217: + "' was not found. Please check the path "
218: + "and try again.");
219: }
220:
221: currentParticle = next;
222: }
223:
224: if (currentParticle.getMinOccurs().intValue() == 0)
225: anyAreOptional = true;
226:
227: if (currentParticle.getParticleType() != SchemaParticle.ELEMENT) {
228: // formatting
229: throw new XmlException(
230: "XPath '"
231: + xpath
232: + "' points to a complex type or other item, not a single element. Stop.");
233: }
234:
235: if (currentParticle.isDefault() && (!anyAreOptional)) {
236: // formatting
237: throw new XmlException(
238: "XPath '"
239: + xpath
240: + "' has a default, but is not optional. This doesn't make sense.");
241: }
242:
243: return new SchemaInfo(currentParticle.isDefault(),
244: anyAreOptional, currentParticle.getDefaultValue());
245: }
246:
247: private static class ElementReturn {
248: private final SchemaParticle particle;
249: private final boolean isOptional;
250:
251: public ElementReturn(SchemaParticle particle, boolean isOptional) {
252: Assert.assertNotNull(particle);
253: this .particle = particle;
254: this .isOptional = isOptional;
255: }
256:
257: public SchemaParticle particle() {
258: return this .particle;
259: }
260:
261: public boolean isOptional() {
262: return this .isOptional;
263: }
264: }
265:
266: private ElementReturn findNextElement(String xpath,
267: String component, SchemaParticle[] children, boolean lastOne)
268: throws XmlException {
269:
270: SchemaParticle next = null;
271: StringBuffer actualChildren = new StringBuffer();
272: boolean optional = false;
273:
274: for (int childIndex = 0; childIndex < children.length; ++childIndex) {
275: String this ChildName = children[childIndex].getName()
276: .getLocalPart();
277: if (childIndex > 0)
278: actualChildren.append(", ");
279: actualChildren.append(this ChildName);
280: if (this ChildName != null
281: && this ChildName.equals(component)) {
282: if (next != null) {
283: // formatting
284: throw new XmlException("Component '" + component
285: + "' of XPath '" + xpath
286: + "' has multiple children named '"
287: + component
288: + "'. We don't support this. Stop.");
289: } else {
290: next = children[childIndex];
291: }
292: }
293: }
294:
295: if ((!lastOne)
296: && next.getParticleType() == SchemaParticle.ELEMENT) {
297: optional = optional || next.getMinOccurs().intValue() == 0;
298: next = next.getType().getContentModel();
299: }
300:
301: if (next == null) {
302: // formatting
303: throw new XmlException("Component '" + component
304: + "' of path '" + xpath
305: + "' was not found. Instead, we found: "
306: + actualChildren);
307: }
308:
309: optional = optional || next.getMinOccurs().intValue() == 0;
310:
311: return new ElementReturn(next, optional);
312: }
313:
314: private void checkParticleType(String component, int particleType,
315: String xpath) throws XmlException {
316: if (particleType != SchemaParticle.ALL
317: && particleType != SchemaParticle.CHOICE
318: && particleType != SchemaParticle.SEQUENCE) {
319: // formatting
320: throw new XmlException("Component '" + component
321: + "' of XPath '" + xpath
322: + "' is a schema particle of type " + particleType
323: + ", not " + SchemaParticle.ALL + " ('all'), "
324: + SchemaParticle.CHOICE + " ('choice'), or "
325: + SchemaParticle.SEQUENCE + " ('sequence'). Stop.");
326: }
327: }
328:
329: private boolean isInterpretableXPath(String xpath) {
330: String[] components = xpath.split("/");
331:
332: for (int i = 0; i < components.length; ++i) {
333: if (!ALLOWED_COMPONENT_PATTERN.matcher(components[i])
334: .matches())
335: return false;
336: }
337:
338: return true;
339: }
340: }
|