001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent;
019:
020: import java.util.LinkedList;
021: import java.util.List;
022:
023: import de.finix.contelligent.core.PropertyType;
024: import de.finix.contelligent.exception.ContelligentExceptionID;
025: import de.finix.contelligent.exception.ContelligentRuntimeException;
026: import de.finix.contelligent.logging.LoggingService;
027: import de.finix.contelligent.util.StringHash;
028:
029: // FIXME: construtor and various method (append, validate, ...) should throw
030: // exception but who wants to modify those millions of lines ...?
031: // alternative: use runtime exception
032:
033: /**
034: * A <code>ComponentPath</code> instance represents a valid path to a
035: * {@link Component}. As many methods within the Contelligent System require
036: * the directory and the name of a component as separate parameters this class
037: * acts as a helper class for parsing a string into {@link #getDir dir} and
038: * {@link #getName name}.
039: */
040: public class ComponentPath implements PropertyType,
041: java.io.Serializable {
042: final static org.apache.log4j.Logger log = LoggingService
043: .getLogger(ComponentPath.class);
044:
045: /** the separator for path-elements */
046: final static public char SEPARATOR = '/';
047:
048: final static public String SEPARATOR_STRING = String
049: .valueOf(SEPARATOR);
050:
051: final static public ComponentPath EMPTY_PATH = new ComponentPath(
052: "", "");
053:
054: final static public ComponentPath ROOT_PATH = new ComponentPath(
055: SEPARATOR_STRING);
056:
057: final static public int VCSIZE_COMPPATH = 246; // == dir + name in table
058:
059: // cMain
060:
061: final static public int VCSIZE_NAME = 43; // == name in table cMain
062:
063: final static public int VCSIZE_DIR = 203; // == dir in table cMain
064:
065: // Keep identical with client:ContelligentComponent.getAllowString()
066: public static final String permittedNameCharacters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@_-.";
067:
068: public static final String permittedPathCharacters = permittedNameCharacters
069: + "/";
070:
071: final String path, pathWithEndToken, dir, name;
072:
073: final private int hash;
074:
075: final private boolean isAbsolute;
076:
077: private int upLevel;
078:
079: private String[] parsedPath = null;
080:
081: /**
082: * Creates a <code>ComponentPath</code> from the given path. The given
083: * path must contain at least one character!
084: */
085: public ComponentPath(String path) {
086: this (path, false);
087: }
088:
089: /**
090: * Creates a <code>ComponentPath</code> from the given path. If parameter
091: * <tt>validatePath</tt> is true the path gets
092: * {@link #validatePath validated} before parsing.
093: */
094: public ComponentPath(String path, boolean validatePath) {
095: if (validatePath) {
096: path = validatePath(path);
097: }
098:
099: int dotdotPosition = path.indexOf("..");
100: if (dotdotPosition != -1 && dotdotPosition > 0) {
101: throw new IllegalArgumentException(
102: ".. is allowed for relative paths only");
103: }
104:
105: String tmp = path;
106:
107: while (tmp.startsWith("../")) {
108: upLevel++;
109: tmp = tmp.substring(3);
110: }
111: if (log.isDebugEnabled())
112: log.debug("<init> - parsing path '" + path + "' ...");
113:
114: int len = path.length();
115: if (len == 1) {
116: if (path.charAt(0) == SEPARATOR) {
117: this .dir = SEPARATOR_STRING;
118: this .name = "";
119: this .path = SEPARATOR_STRING;
120: this .pathWithEndToken = SEPARATOR_STRING;
121: } else {
122: this .dir = "";
123: this .name = path;
124: this .path = path;
125: this .pathWithEndToken = path + SEPARATOR_STRING;
126: }
127: } else {
128: int searchFrom = (path.charAt(len - 1) == SEPARATOR) ? len - 2
129: : len - 1;
130: int cut = path.lastIndexOf(SEPARATOR, searchFrom);
131: if (cut == -1) {
132: // means we have a relative path with one element like 'abc/':
133: this .dir = "";
134: this .name = path.substring(0, searchFrom + 1);
135: } else {
136: this .dir = path.substring(0, cut + 1); // including a trailing
137: // separator
138: this .name = path.substring(cut + 1, searchFrom + 1);
139: }
140: checkPathLength(dir, name);
141: this .path = dir + name;
142: this .pathWithEndToken = this .path + SEPARATOR;
143: }
144: this .hash = this .path.hashCode();
145: isAbsolute = (pathWithEndToken.charAt(0) == SEPARATOR);
146: if (log.isDebugEnabled()) {
147: log.debug("<init> - parsed path '" + path + "' into dir='"
148: + this .dir + "', name='" + this .name + "', path='"
149: + this .path + "', pathWithEndToken='"
150: + this .pathWithEndToken + "', hash=" + this .hash
151: + ".");
152: }
153: }
154:
155: // used by append() and clone() methods
156: private ComponentPath(String dir, String name) {
157: checkPathLength(dir, name);
158: StringBuffer buffer = new StringBuffer();
159: this .dir = dir;
160: this .name = name;
161: buffer.append(dir).append(name);
162: path = buffer.toString(); // dir + name
163: // XXX: this way the EMPTY_PATH gets the SEPARATOR as pathWithEndToken,
164: // is this ok ?
165: buffer.append(SEPARATOR);
166: pathWithEndToken = buffer.toString(); // path + SEPARATOR
167: hash = path.hashCode();
168: isAbsolute = (pathWithEndToken.charAt(0) == SEPARATOR);
169: if (log.isDebugEnabled()) {
170: log.debug("<init(private)> - dir='" + this .dir
171: + "', name='" + this .name + "', path='" + this .path
172: + "', pathWithEndToken='" + this .pathWithEndToken
173: + "', hash=" + this .hash + ".");
174: }
175: }
176:
177: /**
178: * Returns a <code>String[]</code> containing the elements of this path.
179: * See {@link parsePath(String)} for further description.
180: *
181: * @return a <code>String[]</code> value containing the path elements or
182: * an empty <code>String[]</code> if this path is empty.
183: */
184: public String[] getParsedPath() {
185: // we don't need to synchronized here, in the worst case we parse this
186: // path
187: // multiplte times and some threads have different arrays (but with same
188: // content) (rs)
189: if (parsedPath == null) {
190: parsedPath = parsePath();
191: }
192: return parsedPath;
193: }
194:
195: /**
196: * FIXME: this method currently doesn't do anything!
197: *
198: * Validates the given path (and should throw exception if path is invalid).
199: * Validating a path means the following:
200: * <OL>
201: * <LI>check whether the length of the path exceeds maximum</LI>
202: * <LI>check whether path contains any forbidden characters</LI>
203: * <LI>removeType leading or trailing whitespaces from path</LI>
204: * <LI>merge multiple occurances of separators into one</LI>
205: * <LI>MAYBE: call normalize on path that is removeType sequences of a
206: * path-element followed by '..'</LI>
207: * </OL>
208: */
209: public static String validatePath(String path) {
210: return path.trim();
211: }
212:
213: public ComponentPath append(String name) {
214: if (name.indexOf(SEPARATOR) != -1) {
215: throw new IllegalArgumentException("Name '" + name
216: + "' may not contain '" + SEPARATOR + "'");
217: }
218: if (this == EMPTY_PATH) {
219: return new ComponentPath("/", name);
220: } else if (name.startsWith("..")) {
221: return append(new ComponentPath(name));
222: } else if (name.length() == 0) {
223: return this ;
224: } else {
225: return new ComponentPath(this .pathWithEndToken, name);
226: }
227: }
228:
229: public ComponentPath append(ComponentPath toAppend) {
230: if (toAppend == EMPTY_PATH) {
231: return this ;
232: } else {
233: if (this == EMPTY_PATH) {
234: return toAppend;
235: } else {
236: if (toAppend.isRelative()) {
237: if (toAppend.toPath().startsWith("..")) {
238: ComponentPath current = this ;
239: String tmp = toAppend.toPath();
240: while (tmp.startsWith("..")) {
241: current = current.parentPath();
242: if (current == null) {
243: throw new ComponentPathException(
244: ContelligentExceptionID.path_invalidRelative,
245: new Object[] {
246: toAppend.toPath(),
247: this .toPath() });
248: }
249: tmp = tmp.substring(2);
250: if (tmp.startsWith("/")) {
251: tmp = tmp.substring(1);
252: }
253: }
254: if (tmp.length() == 0) {
255: return current;
256: } else {
257: return current
258: .append(new ComponentPath(tmp));
259: }
260: }
261: return new ComponentPath(this .pathWithEndToken
262: + toAppend.dir, toAppend.name);
263: } else {
264: return new ComponentPath(this .path + toAppend.dir,
265: toAppend.name);
266: }
267: }
268: }
269: }
270:
271: public ComponentPath prepend(ComponentPath toPrepend) {
272: return toPrepend.append(this );
273: }
274:
275: public String getDir() {
276: return dir;
277: }
278:
279: public String getName() {
280: return name;
281: }
282:
283: public String toString() {
284: return path;
285: }
286:
287: public String toPath() {
288: return path;
289: }
290:
291: public String getPathWithEndToken() {
292: return pathWithEndToken;
293: }
294:
295: public boolean equals(Object o) {
296: if (this == o) {
297: return true;
298: }
299: if (o instanceof ComponentPath) {
300: return (this .path.equals(((ComponentPath) o).path));
301: }
302: return false;
303: }
304:
305: public int hashCode() {
306: return hash;
307: }
308:
309: /**
310: * Returns true if this path is null or has length zero.
311: *
312: * @return a <code>boolean</code> value
313: */
314: public boolean isEmpty() {
315: return (path.length() == 0);
316: }
317:
318: public boolean isAbsolute() {
319: return isAbsolute;
320: }
321:
322: public boolean isRelative() {
323: return !isAbsolute;
324: }
325:
326: public boolean isDotDotRelative() {
327: return pathWithEndToken.startsWith("..");
328: }
329:
330: /**
331: * Returns true if the given path begins with a {@link #SEPARATOR}.
332: *
333: * @param path
334: * a <code>String</code> value
335: * @return a <code>boolean</code> value
336: */
337: static public boolean isAbsolutePath(String path) {
338: return (path != null && path.charAt(0) == SEPARATOR);
339: }
340:
341: public ComponentPath toAbsolutePath(ComponentPath rootPath) {
342: if (this .isAbsolute()) {
343: return this ;
344: }
345: if (log.isDebugEnabled()) {
346: log.debug("'" + this + "':toAbsolutePath() - using path '"
347: + rootPath + "' as root ...");
348: }
349: return this .prepend(rootPath);
350: }
351:
352: public ComponentPath toRelativePath(ComponentPath rootPath)
353: throws ComponentPathException {
354: if (log.isDebugEnabled())
355: log.debug("'" + this + "':toRelativePath() - using path '"
356: + rootPath + "' as root ...");
357: if (isSubPathOf(rootPath)) {
358: String pathString = this .toString();
359: String rootString = rootPath.getPathWithEndToken();
360: return new ComponentPath(pathString.substring(rootString
361: .length()));
362: } else {
363: StringBuffer relativePath = new StringBuffer();
364: String[] selfParts = this .parsePath();
365: String[] rootParts = rootPath.parsePath();
366: int i = 0;
367:
368: while (true) {
369: if (i == selfParts.length || i == rootParts.length) {
370: break;
371: }
372: if (!selfParts[i].equals(rootParts[i])) {
373: break;
374: }
375: i++;
376: }
377: for (int j = i; j < rootParts.length; j++) {
378: relativePath.append("../");
379: }
380: for (int j = i; j < selfParts.length; j++) {
381: relativePath.append(selfParts[j]).append("/");
382: }
383: relativePath.deleteCharAt(relativePath.length() - 1);
384: return new ComponentPath(relativePath.toString());
385: }
386: }
387:
388: /**
389: * Returns a new <code>ComponentPath</code> object initialized to the
390: * value of the specified String.
391: *
392: * @param s
393: * a <code>String</code> value
394: * @return a <code>PropertyType</code> value
395: */
396: static public PropertyType valueOf(String s)
397: throws PropertyException {
398: s = validatePath(s);
399: if (s == null || s.length() == 0)
400: return EMPTY_PATH;
401: else
402: return new ComponentPath(s);
403: }
404:
405: /**
406: * Returns a <code>String[]</code> containing the elements of the given
407: * path which are determined by means of a {@link #SEPARATOR separator}.
408: * The path doesn't need to start or end with a separator, so with the
409: * default separator '/' either of "/sub/text", "sub/text", "/sub/text/" or
410: * "sub/text/" will result in two path elements 'sub' and 'text'. <BR>
411: * Note that multiple separator sequences like "///" will be merged into a
412: * single separator.
413: *
414: * @param path
415: * a non-empty <code>String</code> value containg the path to
416: * parse.
417: * @return a <code>String[]</code> value containing the path elements or
418: * an empty <code>String[]</code> if the path was empty.
419: */
420: static public String[] parsePath(String path)
421: throws ComponentPathException {
422: return new ComponentPath(path).getParsedPath();
423: }
424:
425: /**
426: * Returns a <code>String[]</code> containing the elements of this path.
427: * See {@link parsePath(String)} for further description.
428: *
429: * @return a <code>String[]</code> value containing the path elements or
430: * an empty <code>String[]</code> if this path is empty.
431: */
432: private String[] parsePath() {
433: if (path.length() == 0)
434: return new String[0];
435: List pathElems = new LinkedList();
436: int pos = (this .isAbsolute() ? 1 : 0);
437: int i = 0;
438: while ((i = pathWithEndToken.indexOf(SEPARATOR, pos)) >= pos) {
439: pathElems.add(pathWithEndToken.substring(pos, i));
440: pos = i + 1;
441: }
442: return (String[]) pathElems
443: .toArray(new String[pathElems.size()]);
444: }
445:
446: /**
447: * Returns true if <tt>subPath</tt> is a sub-path of <tt>path</tt> by
448: * means of path-elements. For example '/my/path2/sub' is a sub-path of
449: * '/my/path2' but '/my/path' is not. Note that two equal paths are <b>not</b>
450: * sub-paths of one another and that both path to check must be absolute!
451: *
452: * @param subPath
453: * a non-empty <code>String</code>, the absolute path to check
454: * whether it is a sub-path.
455: * @param path
456: * a non-empty <code>String</code>, the absolute path to check
457: * <tt>subPath</tt> against
458: * @return a <code>boolean</code> value
459: */
460: public static boolean isSubPathOf(String subPath, String path) {
461: if (!isAbsolutePath(subPath) || !isAbsolutePath(path)) {
462: return false;
463: }
464: int l1 = subPath.length();
465: int l2 = path.length();
466: if (l1 <= l2)
467: return false;
468: if (subPath.charAt(l1 - 1) != SEPARATOR)
469: subPath += SEPARATOR;
470: if (path.charAt(l2 - 1) != SEPARATOR)
471: path += SEPARATOR;
472: return (!(path.equals(subPath)) && (path.startsWith(subPath)));
473: }
474:
475: /**
476: * Returns true if this path is a sub-path of <tt>path</tt> by means of
477: * path-elements. For example '/my/path2/sub' is a sub-path of '/my/path2'
478: * but '/my/path' is not. Note that two equal paths are <b>not</b>
479: * sub-paths of one another and that both path to check must be absolute!
480: *
481: * @param path
482: * the path to check this path against.
483: * @return a <code>boolean</code> value
484: */
485: public boolean isSubPathOf(ComponentPath path) {
486: return ((this .isAbsolute() && path.isAbsolute())
487: && !(this .pathWithEndToken
488: .equals(path.pathWithEndToken)) && this .pathWithEndToken
489: .startsWith(path.pathWithEndToken));
490: }
491:
492: /**
493: * Returns a path where the given <tt>oldParent</tt> is exchanged with
494: * <tt>newParent</tt> or null if this path is not a
495: * {@link #isSubPathOf sub-path} of <tt>oldParent</tt>.
496: *
497: * @param oldParent
498: * a <code>ComponentPath</code> value
499: * @param newParent
500: * a <code>ComponentPath</code> value
501: * @return a <code>ComponentPath</code> value
502: */
503: public ComponentPath exchangeParent(ComponentPath oldParent,
504: ComponentPath newParent) {
505: if (!isSubPathOf(oldParent)) {
506: log
507: .error("'"
508: + this
509: + "':exchangeParent() - this path is not a sub-path of '"
510: + oldParent + "'!");
511: return null;
512: } else {
513: if (this .equals(oldParent)) {
514: return newParent;
515: } else {
516: String oldParentString = oldParent
517: .getPathWithEndToken();
518: int i = this .pathWithEndToken.indexOf(oldParentString);
519: // is never -1 because isSubPathOf() guarantees that.
520: ComponentPath rest = new ComponentPath(this .path
521: .substring(i + oldParentString.length()));
522: return newParent.append(rest);
523: }
524: }
525: }
526:
527: /**
528: * Returns the largest common path among the given paths. If an error occurs
529: * or if any of the paths is relative or if the paths have no path in common
530: * the {@link #ROOT_PATH} is returned.
531: *
532: * @param paths
533: * a <code>ComponentPath[]</code> value
534: * @return a <code>ComponentPath</code> value
535: */
536: public static ComponentPath calculateLargestCommonPath(
537: ComponentPath[] paths) {
538: if (paths == null || paths.length == 0)
539: return ROOT_PATH;
540: if (paths.length == 1)
541: return paths[0];
542:
543: List pathList = new LinkedList();
544: ComponentPath mp = paths[0];
545: // search the path with the fewest number of elements and ensure its at
546: // pos 0
547: // of the list so we can start the comparison at pos 1
548: String[] minPath = mp.getParsedPath();
549: for (int i = 0; i < paths.length; i++) {
550: ComponentPath p = paths[i];
551: if (!p.isAbsolute()) {
552: log.error("calculateLargestCommonPath() - path '" + p
553: + "' is relative => returning root-path.");
554: return ROOT_PATH;
555: }
556: String[] path = p.getParsedPath();
557: if (path.length < minPath.length) {
558: pathList.add(0, path);
559: minPath = path;
560: } else {
561: pathList.add(path);
562: }
563: }
564:
565: String[] lcp = new String[minPath.length];
566: boolean matches = true;
567: int t = 0;
568: while (t < minPath.length) {
569: String s = minPath[t];
570: for (int i = 1; i < paths.length; i++) {
571: String cmp = ((String[]) pathList.get(i))[t];
572: if (!(s.equals(cmp))) {
573: matches = false;
574: break;
575: }
576: }
577: if (matches)
578: lcp[t++] = s;
579: else
580: break;
581: }
582: if (t == 0) {
583: return ROOT_PATH;
584: } else {
585: StringBuffer result = new StringBuffer();
586: for (int i = 0; i < t; i++)
587: result.append(SEPARATOR).append(lcp[i]);
588: return new ComponentPath(result.toString());
589: }
590: }
591:
592: public ComponentPath parentPath() {
593: // log.info("parentPath() - this='"+this+"' ...");
594: if (this .isEmpty() || ROOT_PATH.equals(this )
595: || (this .isRelative() && path.indexOf(SEPARATOR) == -1)) {
596: return null;
597: } else {
598: return new ComponentPath(dir);
599: }
600: }
601:
602: public boolean isRoot() {
603: return this .equals(ROOT_PATH);
604: }
605:
606: public String getStringHash() {
607: return StringHash.getHash16(toPath());
608: }
609:
610: void checkPathLength(String dir, String name) {
611: if ((dir.length() > VCSIZE_DIR)
612: || (name.length() > VCSIZE_NAME)) {
613: throw new ContelligentRuntimeException(
614: ContelligentExceptionID.path_toolong, new Object[] {
615: dir + name, new Integer(VCSIZE_COMPPATH) });
616: }
617: }
618: }
|