/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.exoplatform.services.jcr.impl.core.query.lucene;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.exoplatform.services.jcr.datamodel.IllegalNameException;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.regex.Pattern;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
/**
* <code>Util</code> provides various static utility methods.
*/
public class Util
{
/**
* The logger instance for this class.
*/
private static final Logger log = LoggerFactory.getLogger("exo.jcr.component.core.Util");
/**
* Disposes the document <code>old</code>. Closes any potentially open
* readers held by the document.
*
* @param old the document to dispose.
*/
public static void disposeDocument(final Document old)
{
for (Iterator<Fieldable> it = old.getFields().iterator(); it.hasNext();)
{
Fieldable f = it.next();
try
{
if (f.readerValue() != null)
{
f.readerValue().close();
}
else if (f instanceof TextFieldExtractor)
{
TextFieldExtractor field = (TextFieldExtractor)f;
field.dispose();
}
}
catch (IOException ex)
{
log.warn("Exception while disposing index document: " + ex);
}
}
}
/**
* Depending on the index format this method returns a query that matches
* all nodes that have a property with a given <code>name</code>.
*
* @param name the property name.
* @param version the index format version.
* @return Query that matches all nodes that have a property with the given
* <code>name</code>.
*/
public static Query createMatchAllQuery(String name, IndexFormatVersion version)
{
if (version.getVersion() >= IndexFormatVersion.V2.getVersion())
{
// new index format style
return new JcrTermQuery(new Term(FieldNames.PROPERTIES_SET, name));
}
else
{
return new MatchAllQuery(name);
}
}
/**
* Creates an {@link IOException} with <code>t</code> as its cause.
*
* @param t the cause.
*/
public static IOException createIOException(Throwable t)
{
IOException ex = new IOException(t.getMessage());
ex.initCause(t);
return ex;
}
/**
* Depending on the type of the <code>reader</code> this method either
* closes or releases the reader. The reader is released if it implements
* {@link ReleaseableIndexReader}.
*
* @param reader the index reader to close or release.
* @throws IOException if an error occurs while closing or releasing the
* index reader.
*/
public static void closeOrRelease(final IndexReader reader) throws IOException
{
if (reader instanceof ReleaseableIndexReader)
{
((ReleaseableIndexReader)reader).release();
}
else
{
reader.close();
}
}
/**
* Returns a comparable for the internal <code>value</code>.
*
* @param value an internal value.
* @return a comparable for the given <code>value</code>.
*/
public static Comparable<?> getComparable(ValueData value, int type) throws UnsupportedEncodingException,
IllegalStateException, IOException, IllegalNameException, RepositoryException
{
switch (type)
{
case PropertyType.BINARY :
return null;
case PropertyType.BOOLEAN :
return ComparableBoolean.valueOf(ValueDataUtil.getBoolean(value));
case PropertyType.DATE :
return new Long(ValueDataUtil.getDate(value).getTimeInMillis());
case PropertyType.DOUBLE :
return new Double(ValueDataUtil.getDouble(value));
case PropertyType.LONG :
return new Long(ValueDataUtil.getLong(value));
case PropertyType.NAME :
return new QPathEntry(ValueDataUtil.getName(value), 1);
case PropertyType.PATH :
return ValueDataUtil.getPath(value);
case PropertyType.REFERENCE :
return ValueDataUtil.getReference(value);
case PropertyType.STRING :
return ValueDataUtil.getString(value);
default :
return null;
}
}
/**
* Compares values <code>c1</code> and <code>c2</code>. If the
* values have differing types, then the order is defined on
* the type itself by calling <code>compareTo()</code> on the respective
* type class names.
*
* @param c1 the first value.
* @param c2 the second value.
* @return a negative integer if <code>c1</code> should come before
* <code>c2</code><br> a positive integer if <code>c1</code>
* should come after <code>c2</code><br> <code>0</code> if they
* are equal.
*/
@SuppressWarnings("unchecked")
public static <A, B> int compare(Comparable<A> c1, Comparable<B> c2)
{
if (c1 == c2) // NOSONAR
{
return 0;
}
else if (c1 == null)
{
return -1;
}
else if (c2 == null)
{
return 1;
}
else if (c1.getClass() == c2.getClass())
{
return c1.compareTo((A)c2);
}
else
{
// differing types -> compare class names
String name1 = c1.getClass().getName();
String name2 = c2.getClass().getName();
return name1.compareTo(name2);
}
}
/**
* Creates a regexp from <code>likePattern</code>.
*
* @param likePattern the pattern.
* @return the regular expression <code>Pattern</code>.
*/
public static Pattern createRegexp(String likePattern)
{
// - escape all non alphabetic characters
// - escape constructs like \<alphabetic char> into \\<alphabetic char>
// - replace non escaped _ % into . and .*
StringBuilder regexp = new StringBuilder();
boolean escaped = false;
for (int i = 0; i < likePattern.length(); i++)
{
if (likePattern.charAt(i) == '\\')
{
if (escaped)
{
regexp.append("\\\\");
escaped = false;
}
else
{
escaped = true;
}
}
else
{
if (Character.isLetterOrDigit(likePattern.charAt(i)))
{
if (escaped)
{
regexp.append("\\\\").append(likePattern.charAt(i));
escaped = false;
}
else
{
regexp.append(likePattern.charAt(i));
}
}
else
{
if (escaped)
{
regexp.append('\\').append(likePattern.charAt(i));
escaped = false;
}
else
{
switch (likePattern.charAt(i))
{
case '_' :
regexp.append('.');
break;
case '%' :
regexp.append(".*");
break;
default :
regexp.append('\\').append(likePattern.charAt(i));
}
}
}
}
}
return Pattern.compile(regexp.toString(), Pattern.DOTALL);
}
/**
* Returns length of the internal value.
*
* @param value a value.
* @param propType
* @return the length of the internal value or <code>-1</code> if the length
* cannot be determined.
*/
public static long getLength(ValueData value, int propType)
{
if (propType == PropertyType.BINARY)
{
return value.getLength();
}
else if (propType == PropertyType.NAME || propType == PropertyType.PATH)
{
return -1;
}
else
{
return value.toString().length();
}
}
}