/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.datamodel; import org.exoplatform.commons.utils.QName; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.util.ArrayList; import java.util.List; import javax.jcr.PathNotFoundException; /** * Created by The eXo Platform SAS. * * @author Gennady Azarenkov * @version $Id: QPath.java 11907 2008-03-13 15:36:21Z ksm $ */ public class QPath implements Comparable<QPath> { /** * Logger. */ protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.QPath"); /** * QPath prefix delimiter. */ public static final String PREFIX_DELIMITER = ":"; /** * Names storage. */ private final QPathEntry[] names; /** * Path hash code. */ private final int hashCode; /** * String representation of the path. */ private String stringName; /** * QPath constructor. * * @param names */ public QPath(QPathEntry[] names) { this.names = names; final int prime = 31; int hash = names.length > 0 ? 1 : super.hashCode(); for (QPathEntry entry : names) { hash = prime * hash + entry.hashCode(); hash = prime * hash + entry.getIndex(); } this.hashCode = hash; } /** * Tell if the path is absolute. * * @return boolean */ public boolean isAbsolute() { if (names[0].getIndex() == 1 && names[0].getName().length() == 0 && names[0].getNamespace().length() == 0) return true; else return false; } /** * @return parent path */ public QPath makeParentPath() { return makeAncestorPath(1); } /** * Makes ancestor path by relative degree (For ex relativeDegree == 1 means parent path etc) * * @param relativeDegree * @return */ public QPath makeAncestorPath(int relativeDegree) { int entryCount = getLength() - relativeDegree; QPathEntry[] ancestorEntries = new QPathEntry[entryCount]; for (int i = 0; i < entryCount; i++) { QPathEntry entry = names[i]; ancestorEntries[i] = new QPathEntry(entry.getNamespace(), entry.getName(), entry.getIndex()); } return new QPath(ancestorEntries); } /** * Get relative path with degree. * * @param relativeDegree * - degree value * @return arrayf of QPathEntry * @throws IllegalPathException * - if the degree is invalid */ public QPathEntry[] getRelPath(int relativeDegree) throws IllegalPathException { int len = getLength() - relativeDegree; if (len < 0) throw new IllegalPathException("Relative degree " + relativeDegree + " is more than depth for " + getAsString()); QPathEntry[] relPath = new QPathEntry[relativeDegree]; System.arraycopy(names, len, relPath, 0, relPath.length); return relPath; } /** * @return array of its path's names */ public QPathEntry[] getEntries() { return names; } /** * @return depth of this path calculates as size of names array - 1. For ex root's depth=0 etc. */ public int getDepth() { return names.length - 1; } /** * @param ancestorPath * @return if this path is descendant of given ancestor */ public boolean isDescendantOf(final QPath ancestorPath) { final InternalQName[] ancestorNames = ancestorPath.names; if (names.length - ancestorNames.length <= 0) return false; for (int i = ancestorNames.length - 1; i >= 0; i--) { if (!names[i].equals(ancestorNames[i])) return false; } return true; } /** * @param anotherPath * @param childOnly * if == true only direct children of the path will be taking in account * @return if this path is descendant of another one */ public boolean isDescendantOf(final QPath anotherPath, final boolean childOnly) { final InternalQName[] anotherNames = anotherPath.names; int depthDiff = names.length - anotherNames.length; if (depthDiff <= 0 || (childOnly && depthDiff != 1)) return false; for (int i = anotherNames.length - 1; i >= 0; i--) { if (!anotherNames[i].equals(names[i])) return false; } return true; } /** * Get common ancestor path. * * @param firstPath * @param secondPath * @return The common ancestor of two paths. * @throws PathNotFoundException */ public static QPath getCommonAncestorPath(QPath firstPath, QPath secondPath) throws PathNotFoundException { if (!firstPath.getEntries()[0].equals(secondPath.getEntries()[0])) { throw new PathNotFoundException("For the given ways there is no common ancestor."); } List<QPathEntry> caEntries = new ArrayList<QPathEntry>(); for (int i = 0; i < firstPath.getEntries().length; i++) { if (firstPath.getEntries()[i].equals(secondPath.getEntries()[i])) { caEntries.add(firstPath.getEntries()[i]); } else { break; } } return new QPath(caEntries.toArray(new QPathEntry[caEntries.size()])); } /** * @return last QPathEntry of this path */ public InternalQName getName() { return names[getLength() - 1]; } /** * @return index of last QPathEntry of this paths */ public int getIndex() { return names[getLength() - 1].getIndex(); } /** * @return length of names array */ protected int getLength() { return names.length; } /** * Get String representation. * * @return String */ public String getAsString() { if (stringName == null) { StringBuilder str = new StringBuilder(); for (int i = 0; i < getLength(); i++) { str.append(names[i].getAsString(true)); } stringName = str.toString(); } return stringName; } /** * {@inheritDoc} */ @Override public String toString() { return super.toString() + " (" + getAsString() + ")"; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof QPath)) return false; return hashCode == o.hashCode(); } /** * {@inheritDoc} */ public int compareTo(QPath compare) { if (compare.equals(this)) return 0; QPathEntry[] e1 = names; QPathEntry[] e2 = compare.getEntries(); int len1 = e1.length; int len2 = e2.length; int k = 0; int lim = Math.min(len1, len2); while (k < lim) { QPathEntry c1 = e1[k]; QPathEntry c2 = e2[k]; if (!c1.isSame(c2)) { return c1.compareTo(c2); } k++; } return len1 - len2; } @Override public int hashCode() { return hashCode; } // Factory methods --------------------------- /** * Parses string and make internal path from it. * * @param qPath * - String to be parsed * @return QPath * @throws IllegalPathException * - if string is invalid */ public static QPath parse(String qPath) throws IllegalPathException { if (qPath == null) throw new IllegalPathException("Bad internal path '" + qPath + "'"); if (qPath.length() < 2 || !qPath.startsWith("[]")) throw new IllegalPathException("Bad internal path '" + qPath + "'"); int uriStart = 0; List<QPathEntry> entries = new ArrayList<QPathEntry>(); while (uriStart >= 0) { uriStart = qPath.indexOf("[", uriStart); int uriFinish = qPath.indexOf("]", uriStart); String uri = qPath.substring(uriStart + 1, uriFinish); int tmp = qPath.indexOf("[", uriFinish); // next token if (tmp == -1) { tmp = qPath.length(); uriStart = -1; } else uriStart = tmp; String localName = qPath.substring(uriFinish + 1, tmp); int index = 0; int ind = localName.indexOf(PREFIX_DELIMITER); if (ind != -1) { // has index index = Integer.parseInt(localName.substring(ind + 1)); localName = localName.substring(0, ind); } else { if (uriStart > -1) throw new IllegalPathException("Bad internal path '" + qPath + "' each intermediate name should have index"); } entries.add(new QPathEntry(uri, localName, index)); } return new QPath(entries.toArray(new QPathEntry[entries.size()])); } /** * Make child path using JCR internal QName and index 1. <br> * * @param parent * - parent QPath * @param name * - Item InternalQName * @return new QPath */ public static QPath makeChildPath(final QPath parent, final InternalQName name) { return makeChildPath(parent, name, 1); } /** * Make child path using QName and Item index. <br> * * @param parent * - parent QPath * @param name * - Item QName * @param itemIndex * - Item index * @return new QPath */ public static QPath makeChildPath(final QPath parent, final QName name, final int itemIndex) { QPathEntry[] parentEntries = parent.getEntries(); QPathEntry[] names = new QPathEntry[parentEntries.length + 1]; int index = 0; for (QPathEntry pname : parentEntries) { names[index++] = pname; } names[index] = new QPathEntry(name.getNamespace(), name.getName(), itemIndex); QPath path = new QPath(names); return path; } /** * Make child path using QName and Item index. <br> * * @param parent * - parent QPath * @param name * - Item QName * @param itemIndex * - Item index * @param id * - Item id * @return new QPath */ public static QPath makeChildPath(final QPath parent, final QName name, final int itemIndex, String id) { QPathEntry[] parentEntries = parent.getEntries(); QPathEntry[] names = new QPathEntry[parentEntries.length + 1]; int index = 0; for (QPathEntry pname : parentEntries) { names[index++] = pname; } names[index] = new QPathEntry(name.getNamespace(), name.getName(), itemIndex, id); QPath path = new QPath(names); return path; } /** * Make child path using array of QPath entries (relative path). <br> * * @param parent * - parent QPath * @param relEntries * - QPathEntry array * @return new QPath */ public static QPath makeChildPath(final QPath parent, final QPathEntry[] relEntries) { final QPathEntry[] parentEntries = parent.getEntries(); final QPathEntry[] names = new QPathEntry[parentEntries.length + relEntries.length]; int index = 0; for (QPathEntry name : parentEntries) names[index++] = name; for (QPathEntry name : relEntries) names[index++] = name; QPath path = new QPath(names); return path; } /** * Make child path using QPath entry. <br> * * Will replace <code>makeChildPath(final QPath parent, final InternalQName name)</code> for cases * when path entry already exists. * * <br> NOTE: it's important for same-name-siblings Items too. * * @param parent * - parent QPath * @param relEntry * - QPathEntry instance * @return new QPath * * @since 1.10 */ public static QPath makeChildPath(final QPath parent, final QPathEntry relEntry) { final QPathEntry[] parentEntries = parent.getEntries(); final QPathEntry[] names = new QPathEntry[parentEntries.length + 1]; int index = 0; for (QPathEntry name : parentEntries) names[index++] = name; names[index] = relEntry; QPath path = new QPath(names); return path; } }