001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.shared;
034:
035: import com.flexive.shared.content.FxPK;
036: import com.flexive.shared.exceptions.FxInvalidParameterException;
037: import org.apache.commons.lang.StringUtils;
038:
039: import java.io.Serializable;
040: import java.util.ArrayList;
041: import java.util.List;
042: import java.util.regex.Matcher;
043: import java.util.regex.Pattern;
044:
045: /**
046: * A single XPath element (alias and multiplicity)
047: *
048: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
049: */
050: public class XPathElement implements Serializable {
051: private static final long serialVersionUID = 2037392183607142045L;
052: private static String PK = "@pk=(NEW|\\d*\\.(LIVE|MAX|\\d*))";
053: /**
054: * First element must start with a "/",
055: * an XPath element must start with a letter followed by an optional letter/number/underscore combination
056: * and may end with an optional multiplicity like [x] where x is a number
057: */
058: private static Pattern XPathPattern = Pattern
059: .compile("([A-Z][A-Z_0-9]{0,}\\["
060: + PK
061: + "\\]){0,1}(\\/[A-Z][A-Z_0-9]{0,}(\\[[0-9]{1,}\\]){0,1}){1,}");
062: private static Pattern PKPattern = Pattern.compile(PK);
063: private static Pattern doubleSlashPattern = Pattern
064: .compile("[\\/]{2,}");
065: private String alias;
066: private int index;
067: private boolean indexDefined;
068: private static final List<XPathElement> EMPTY = new ArrayList<XPathElement>(
069: 0);
070:
071: /**
072: * Ctor
073: *
074: * @param alias alias to use
075: * @param index multiplicity to apply
076: * @param indexDefined was the multiplicity explicitly defined?
077: */
078: public XPathElement(String alias, int index, boolean indexDefined) {
079: this .alias = alias;
080: this .index = index;
081: this .indexDefined = indexDefined;
082: }
083:
084: /**
085: * Getter for the alias
086: *
087: * @return alias
088: */
089: public String getAlias() {
090: return alias;
091: }
092:
093: /**
094: * Getter for the multiplicity
095: *
096: * @return multiplicity
097: */
098: public int getIndex() {
099: return index;
100: }
101:
102: /**
103: * Setter for the multiplicity
104: *
105: * @param index the multiplicity to apply
106: */
107: public void setIndex(int index) {
108: this .index = index;
109: }
110:
111: /**
112: * Was the multiplicity explicitly defined?
113: *
114: * @return multiplicity explicitly defined
115: */
116: public boolean isIndexDefined() {
117: return indexDefined;
118: }
119:
120: /**
121: * get FQN of the alias
122: *
123: * @return FQ alias
124: */
125: @Override
126: public String toString() {
127: return alias + "[" + index + "]";
128: }
129:
130: /**
131: * {@inheritDoc}
132: */
133: @Override
134: public boolean equals(Object obj) {
135: return obj instanceof XPathElement
136: && ((XPathElement) obj).getAlias().equals(
137: this .getAlias())
138: && ((XPathElement) obj).getIndex() == this .getIndex();
139: }
140:
141: /**
142: * {@inheritDoc}
143: */
144: @Override
145: public int hashCode() {
146: return this .getAlias().hashCode() + this .getIndex();
147: }
148:
149: /**
150: * Split an XPath into its elements
151: *
152: * @param XPath
153: * @return XPathElement array
154: * @throws FxInvalidParameterException for invalid elements
155: */
156: public static List<XPathElement> split(String XPath)
157: throws FxInvalidParameterException {
158: if (StringUtils.isEmpty(XPath))
159: return EMPTY;
160: if (XPath.charAt(0) != '/' && XPath.indexOf('/') > 0) {
161: //we have a full qualified XPath with type name that needs to be stripped
162: XPath = XPath.substring(XPath.indexOf('/'));
163: }
164: if (!isValidXPath(XPath))
165: throw new FxInvalidParameterException("XPATH",
166: "ex.xpath.invalid", XPath);
167: String[] xp = XPath.substring(1).split("\\/"); //skip first '/' to avoid empty entries
168: List<XPathElement> elements = new ArrayList<XPathElement>(
169: xp.length);
170: for (String xpcurr : xp) {
171: elements.add(toElement(XPath, xpcurr));
172: }
173: return elements;
174: }
175:
176: /**
177: * Get the last (rightmost) element of an XPath
178: *
179: * @param XPath
180: * @return last (rightmost) element of an XPath
181: * @throws FxInvalidParameterException
182: */
183: public static XPathElement lastElement(String XPath)
184: throws FxInvalidParameterException {
185: if (StringUtils.isEmpty(XPath) || !isValidXPath(XPath))
186: throw new FxInvalidParameterException("XPATH",
187: "ex.xpath.invalid", XPath);
188: return toElement(XPath, XPath
189: .substring(XPath.lastIndexOf('/') + 1));
190: }
191:
192: /**
193: * Convert an alias of an XPath to an element
194: *
195: * @param XPath full XPath, only used if exception is thrown
196: * @param alias alias to convert to an XPathElement
197: * @return XPathElement
198: * @throws FxInvalidParameterException
199: */
200: public static XPathElement toElement(String XPath, String alias)
201: throws FxInvalidParameterException {
202: if (StringUtils.isEmpty(alias) || alias.indexOf('/') >= 0)
203: throw new FxInvalidParameterException("XPATH",
204: "ex.xpath.element.invalid", alias, XPath);
205: try {
206: if (alias.indexOf('[') > 0)
207: return new XPathElement(alias.substring(0,
208: alias.indexOf('[')).toUpperCase(), Integer
209: .valueOf(alias.substring(
210: alias.indexOf('[') + 1,
211: alias.length() - 1)), true);
212: return new XPathElement(alias.toUpperCase(), 1, false);
213: } catch (Exception e) {
214: throw new FxInvalidParameterException("XPATH",
215: "ex.xpath.element.invalid", alias, XPath);
216: }
217: }
218:
219: /**
220: * Check if this XPath is valid
221: *
222: * @param XPath
223: * @return valid or not
224: */
225: public static boolean isValidXPath(String XPath) {
226: if ("/".equals(XPath))
227: return true;
228: return !StringUtils.isEmpty(XPath)
229: && XPathPattern.matcher(XPath).matches();
230: }
231:
232: /**
233: * Get the XPath of an array of XPathElements with multiplicities
234: *
235: * @param xpe list containing XPathElement
236: * @return XPath
237: */
238: public static String toXPath(List<XPathElement> xpe) {
239: if (xpe == null || xpe.size() == 0)
240: return "/";
241: StringBuffer XPath = new StringBuffer(100);
242: for (XPathElement xp : xpe) {
243: XPath.append('/').append(xp.getAlias()).append('[').append(
244: xp.getIndex()).append(']');
245: }
246: return XPath.toString();
247: }
248:
249: /**
250: * Get the XPath of an array of XPathElements without multiplicities
251: *
252: * @param xpe list containing XPathElement
253: * @return XPath
254: */
255: public static String toXPathNoMult(List<XPathElement> xpe) {
256: if (xpe == null || xpe.size() == 0)
257: return "/";
258: StringBuffer XPath = new StringBuffer(100);
259: for (XPathElement xp : xpe) {
260: XPath.append('/').append(xp.getAlias());
261: }
262: return XPath.toString();
263: }
264:
265: /**
266: * Get the given XPath with full multiplicity information
267: *
268: * @param XPath XPath
269: * @return XPath with full multiplicity information
270: * @throws FxInvalidParameterException for invalid XPath
271: */
272: public static String toXPathMult(String XPath)
273: throws FxInvalidParameterException {
274: if (StringUtils.isEmpty(XPath) || "/".equals(XPath))
275: return "/";
276: XPath = XPath.toUpperCase();
277: String type = null;
278: if (XPath.charAt(0) != '/' && XPath.indexOf('/') > 0) {
279: //we have a full qualified XPath with type name that needs to be stripped temporarily
280: type = XPath.substring(0, XPath.indexOf('/'));
281: XPath = XPath.substring(XPath.indexOf('/'));
282: }
283: if (!isValidXPath(XPath))
284: throw new FxInvalidParameterException("XPATH",
285: "ex.xpath.invalid", XPath);
286: String[] xp = XPath.substring(1).split("\\/"); //skip first '/' to avoid empty entries
287: StringBuffer xpc = new StringBuffer(XPath.length() + 10);
288: for (String xpcurr : xp) {
289: xpc.append('/');
290: if (xpcurr.indexOf('[') > 0)
291: xpc.append(xpcurr);
292: else
293: xpc.append(xpcurr).append("[1]");
294: }
295: if (type != null)
296: return type + xpc.toString();
297: return xpc.toString();
298: }
299:
300: /**
301: * Get the given XPath with no indices
302: *
303: * @param XPath XPath with indices
304: * @return XPath with indices stripped
305: * @throws FxInvalidParameterException for invalid XPath
306: */
307: public static String toXPathNoMult(String XPath)
308: throws FxInvalidParameterException {
309: if (StringUtils.isEmpty(XPath) || "/".equals(XPath))
310: return "/";
311: XPath = XPath.toUpperCase();
312: String type = null;
313: if (XPath.charAt(0) != '/' && XPath.indexOf('/') > 0) {
314: //we have a full qualified XPath with type name that needs to be stripped temporarily
315: type = XPath.substring(0, XPath.indexOf('/'));
316: if (type.indexOf('[') > 0)
317: type = type.substring(0, type.indexOf('['));
318: XPath = XPath.substring(XPath.indexOf('/'));
319: }
320: if (!isValidXPath(XPath))
321: throw new FxInvalidParameterException("XPATH",
322: "ex.xpath.invalid", XPath);
323: String[] xp = XPath.substring(1).split("\\/"); //skip first '/' to avoid empty entries
324: StringBuffer xpc = new StringBuffer(XPath.length() + 10);
325: for (String xpcurr : xp) {
326: xpc.append('/');
327: if (xpcurr.indexOf('[') > 0)
328: xpc.append(xpcurr.substring(0, xpcurr.indexOf('[')));
329: else
330: xpc.append(xpcurr);
331: }
332: if (type != null)
333: return type + xpc.toString();
334: return xpc.toString();
335: }
336:
337: /**
338: * Get the FQ indices of an XPath as an int array
339: *
340: * @param XPath the xpath to examine
341: * @return FQ indices of an XPath as an int array
342: * @throws FxInvalidParameterException on errors
343: */
344: public static int[] getIndices(String XPath)
345: throws FxInvalidParameterException {
346: List<XPathElement> xpe = split(XPath);
347: int[] mult = new int[xpe.size()];
348: for (int i = 0; i < mult.length; i++)
349: mult[i] = xpe.get(i).getIndex();
350: return mult;
351: }
352:
353: /**
354: * Build an XPath from the given elements
355: *
356: * @param leadingSlash prepend a leading slash character?
357: * @param elements elements that build the XPath
358: * @return XPath
359: */
360: public static String buildXPath(boolean leadingSlash,
361: String... elements) {
362: StringBuffer XPath = new StringBuffer(100);
363: for (String element : elements) {
364: if (element == null)
365: continue;
366: XPath.append('/');
367: if (element.length() > 0 && element.charAt(0) == '/')
368: XPath.append(element.substring(1));
369: else
370: XPath.append(element);
371: if (XPath.length() > 1
372: && XPath.charAt(XPath.length() - 1) == '/')
373: XPath.deleteCharAt(XPath.length() - 1);
374: }
375: if (XPath.length() > 0) {
376: if (XPath.charAt(0) == '/' && !leadingSlash)
377: XPath.deleteCharAt(0);
378: } else if (XPath.length() == 0 && leadingSlash)
379: XPath.append('/');
380: return doubleSlashPattern.matcher(XPath).replaceAll("/")
381: .toUpperCase();
382: }
383:
384: /**
385: * Strip leading types from an XPath if present
386: *
387: * @param XPath the XPath
388: * @return XPath without leading type
389: */
390: public static String stripType(String XPath) {
391: assert XPath != null : "XPath was null!";
392: if (!XPath.startsWith("/"))
393: return XPath.substring(XPath.indexOf('/')).toUpperCase();
394: return XPath.toUpperCase();
395: }
396:
397: /**
398: * Extract the primary key stored in the given XPath. If no PK is contained
399: * in the XPath, a FxRuntimeException is thrown.
400: *
401: * @param xPath the xpath
402: * @return the primary key stored in the given XPath
403: * @throws com.flexive.shared.exceptions.FxRuntimeException if the given xpath is invalid or contains no PK
404: */
405: public static FxPK getPK(String xPath) {
406: FxSharedUtils.checkParameterEmpty(xPath, "xpath");
407: final Matcher matcher = PKPattern.matcher(xPath);
408: if (!matcher.find()) {
409: throw new FxInvalidParameterException("xpath",
410: "ex.xpath.element.noPk", xPath)
411: .asRuntimeException();
412: }
413: return FxPK.fromString(matcher.group(1));
414: }
415: }
|