/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. 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 com.esri.gpt.catalog.lucene;
import com.esri.gpt.catalog.schema.indexable.tp.TpInterval;
import com.esri.gpt.catalog.schema.indexable.tp.TpIntervals;
import com.esri.gpt.framework.util.Val;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.NumericField;
/**
* Represents the time periods associated with a document.
*/
public class TimeperiodProperty extends Storeable {
/** class variables ========================================================= */
/** The Logger */
private static Logger LOGGER = Logger.getLogger(TimeperiodProperty.class.getName());
/** instance variables ====================================================== */
private String intervalMetaFieldName;
private String multiplicityFieldName;
private String summaryMetaFieldName;
private int precisionStep = 4;
/** constructors ============================================================ */
/**
* Constructs with a supplied name.
* @param name the property name
*/
public TimeperiodProperty(String name) {
super(name);
this.intervalMetaFieldName = this.getName()+".imeta";
this.multiplicityFieldName = this.getName()+".num";
this.summaryMetaFieldName = this.getName()+".meta";
}
/** methods ================================================================= */
/**
* Appends underlying fields to a document prior to writing the
* document to the index.
* @param document the Lucene document
*/
@Override
public void appendForWrite(Document document) {
// get the analyzed time period intervals
boolean bWasInvalid = false;
TpIntervals intervals = null;
Object value = null;
if ((getValues() != null) && (getValues().length == 1)) {
value = getValues()[0];
if (value instanceof TpIntervals) {
intervals = (TpIntervals)value;
}
}
if (intervals == null) {
intervals = new TpIntervals();
bWasInvalid = true;
}
if (intervals.size() > 1) intervals.sort();
long nNow = System.currentTimeMillis();
long nLower = Long.MAX_VALUE;
long nUpper = Long.MIN_VALUE;
int nInterval = 0;
boolean bHasUnknown = false;
boolean bHasNow = false;
boolean bHasLowerNow = false;
boolean bHasUpperNow = false;
boolean bHasDeterminate = false;
boolean bLastWasUpperNow = false;
TpInterval firstInterval = null;
List<String> lIMetaTerms = new ArrayList<String>();
List<String> lSMetaTerms = new ArrayList<String>();
ArrayList<Fieldable> lFields = new ArrayList<Fieldable>();
int nIntervals = intervals.size();
int nLastInterval = (nIntervals - 1);
for (int i=0;i<nIntervals;i++) {
boolean bLastInterval = (i == nLastInterval);
TpInterval interval = intervals.get(i);
String sCurInd = Val.chkStr(interval.getIndeterminate());
if (sCurInd.equals("unknown")) {
bHasUnknown = true;
} else {
nInterval++;
String sLowerField = this.getLowerFieldName(nInterval);
String sUpperField = this.getUpperFieldName(nInterval);
long nCurLower = interval.getLower().longValue();
long nCurUpper = interval.getUpper().longValue();
lFields.add(this.makeBoundaryField(sLowerField,nCurLower));
lFields.add(this.makeBoundaryField(sUpperField,nCurUpper));
if (firstInterval == null) {
firstInterval = interval;
}
if (sCurInd.equals("now")) {
bHasNow = true;
lIMetaTerms.add(this.getMetaValue("now",nInterval));
} else if (sCurInd.equals("now.lower")) {
bHasLowerNow = true;
lIMetaTerms.add(this.getMetaValue("now.l",nInterval));
if (nCurUpper > nUpper) {
nUpper = nCurUpper;
}
} else if (sCurInd.equals("now.upper")) {
bHasUpperNow = true;
if (bLastInterval) bLastWasUpperNow = true;
lIMetaTerms.add(this.getMetaValue("now.u",nInterval));
if (nCurLower < nLower) {
nLower = nCurLower;
}
} else {
bHasDeterminate = true;
lIMetaTerms.add(this.getMetaValue("determinate",nInterval));
if (nCurLower < nLower) {
nLower = nCurLower;
}
if (nCurUpper > nUpper) {
nUpper = nCurUpper;
}
}
}
}
int nMultiplicity = lFields.size() / 2;
boolean bIsUnknown = bHasUnknown && (nMultiplicity == 0);
boolean bIsDeterminate = bHasDeterminate && !bHasUnknown &&
!bHasNow && !bHasLowerNow && !bHasUpperNow;
boolean bIs1Determinate = bIsDeterminate && (nMultiplicity == 1);
boolean bIsIndeterminate = bHasNow || bHasLowerNow || bHasUpperNow;
boolean bIs1Indeterminate = bIsIndeterminate && (nMultiplicity == 1);
boolean bWasEmpty = !bWasInvalid && (nIntervals == 0);
if (bIsDeterminate) lSMetaTerms.add("isDeterminate");
if (bIs1Determinate) lSMetaTerms.add("is1Determinate");
if (bIsIndeterminate) lSMetaTerms.add("isIndeterminate");
if (bIs1Indeterminate) lSMetaTerms.add("is1Indeterminate");
if (bIsUnknown) lSMetaTerms.add("isUnknown");
if (bHasDeterminate) lSMetaTerms.add("hasDeterminate");
if (bHasUnknown) lSMetaTerms.add("hasUnknown");
if (bHasNow) lSMetaTerms.add("hasNow");
if (bHasLowerNow) lSMetaTerms.add("hasLowerNow");
if (bHasUpperNow) lSMetaTerms.add("hasUpperNow");
if (bWasInvalid) lSMetaTerms.add("wasInvalid");
if (bWasEmpty) lSMetaTerms.add("wasEmpty");
// make the summary interval (for the document minLower->maxUpper)
nInterval = 0;
String sLowerField = this.getLowerFieldName(nInterval);
String sUpperField = this.getUpperFieldName(nInterval);
if (bIsDeterminate) {
String sMeta = "determinate";
lFields.add(0,this.makeBoundaryField(sUpperField,nUpper));
lFields.add(0,this.makeBoundaryField(sLowerField,nLower));
lIMetaTerms.add(0,this.getMetaValue(sMeta,nInterval));
} else if (nMultiplicity == 1) {
TpInterval interval = firstInterval;
String sCurInd = Val.chkStr(interval.getIndeterminate());
String sMeta = null;
if (sCurInd.equals("now")) {
sMeta = "now";
} else if (sCurInd.equals("now.lower")) {
sMeta = "now.l";
} else if (sCurInd.equals("now.upper")) {
sMeta = "now.u";
}
if (sMeta != null) {
lFields.add(0,this.makeBoundaryField(sUpperField,nUpper));
lFields.add(0,this.makeBoundaryField(sLowerField,nLower));
lIMetaTerms.add(0,this.getMetaValue(sMeta,nInterval));
}
} else if (nMultiplicity > 1) {
// check for a sequential set of intervals ending with now
// e.g. 2008-08-01..2009-08-31 , 2009-09-01..2010-04-15 , 2010-04-16..now
boolean bUseUpperNowSummary = false;
if (!bHasUnknown && !bHasNow && !bHasLowerNow &&
bHasUpperNow && bLastWasUpperNow) {
if ((nLower != Long.MIN_VALUE) && (nLower != Long.MAX_VALUE)) {
if ((nUpper != Long.MIN_VALUE) && (nUpper != Long.MAX_VALUE)) {
if (nUpper < nNow) {
bUseUpperNowSummary = true;
}
}
}
}
if (!bUseUpperNowSummary) {
// not sure how to generate the summary interval here,
// without a summary the document cannot fall "within"
} else {
lFields.add(0,this.makeBoundaryField(sUpperField,Long.MAX_VALUE));
lFields.add(0,this.makeBoundaryField(sLowerField,nLower));
lIMetaTerms.add(0,this.getMetaValue("now.u",nInterval));
}
}
// add the multiplicity field
boolean bIndexMultiplicity = true;
if (bIndexMultiplicity) {
String sName = this.multiplicityFieldName;
NumericField f = new NumericField(sName,this.precisionStep,Field.Store.YES,true);
f.setLongValue(nMultiplicity);
lFields.add(0,f);
}
// add the interval meta terms field
if (lIMetaTerms.size() > 0) {
String sName = this.intervalMetaFieldName;
String sValue = "meta-terms";
Field f = new Field(sName,sValue,Field.Store.YES,Field.Index.ANALYZED,Field.TermVector.NO);
String[] aMetaTerms = lIMetaTerms.toArray(new String[lIMetaTerms.size()]);
f.setTokenStream(new MetaTermsTokenStream(aMetaTerms));
lFields.add(0,f);
}
// add the summary meta terms field
if (lSMetaTerms.size() > 0) {
String sName = this.summaryMetaFieldName;
String sValue = "meta-terms";
Field f = new Field(sName,sValue,Field.Store.YES,Field.Index.ANALYZED,Field.TermVector.NO);
String[] aMetaTerms = lSMetaTerms.toArray(new String[lSMetaTerms.size()]);
f.setTokenStream(new MetaTermsTokenStream(aMetaTerms));
lFields.add(0,f);
}
// add the interval fields
for (Fieldable fld: lFields) {
if (LOGGER.isLoggable(Level.FINER)) {
StringBuffer sb = new StringBuffer();
sb.append("Appending field:\n ");
sb.append(" name=\"").append(fld.name()).append("\"");
sb.append(" storageOption=\"").append(fld.isStored()).append("\"");
sb.append("\n storeValue=\"").append(fld.stringValue()).append("\"");
LOGGER.finer(sb.toString());
}
document.add(fld);
}
}
/**
* Appends underlying fields to a document prior to writing the
* document to the index.
* @param document the Lucene document
* @param value the input value to write
*/
@Override
public void appendForWrite(Document document, Object value) {
// Not applicable
}
/**
* Makes the lower boundary field name associated with an interval index.
* <br/>Interval 0 is the summary interval for the document.
* @param interval the interval index
* @return the name
*/
private String getLowerFieldName(int interval) {
//if (interval == 0) return this.getName()+".l.d";
//else return this.getName()+".l."+interval;
return this.getName()+".l."+interval;
}
/**
* Makes the meta value associated with an interval index.
* <br/>Interval 0 is the summary interval for the document.
* @param type the value predicate
* @param interval the interval index
* @return the name
*/
private String getMetaValue(String type, int interval) {
//if (interval == 0) return type+".d";
//else return type+"."+interval;
return type+"."+interval;
}
/**
* Makes the upper boundary field name associated with an interval index.
* <br/>Interval 0 is the summary interval for the document.
* @param interval the interval index
* @return the name
*/
private String getUpperFieldName(int interval) {
return this.getName()+".u."+interval;
//String s = this.getName()+".bnd.u";
//if (interval == 0) return s;
//else return s+"."+interval;
//if (interval == 0) return this.getName()+".u.d";
//else return this.getName()+".u."+interval;
}
/**
* Makes an interval boundary field.
* @param name the field name
* @param value the field value
* @return the field
*/
private NumericField makeBoundaryField(String name, Long value) {
NumericField f = new NumericField(name,this.precisionStep,Field.Store.YES,true);
f.setLongValue(value);
return f;
}
/**
* A stream to provide the meta terms for a time period.
*/
private class MetaTermsTokenStream extends TokenStream {
private int currentIndex = -1;
private TermAttribute termAttribute = addAttribute(TermAttribute.class);
private int termCount = 0;
private String[] terms;
public MetaTermsTokenStream(String[] terms) {
this.terms = terms;
this.termCount = this.terms.length;
}
@Override
public boolean incrementToken() throws IOException {
this.currentIndex++;
if (this.currentIndex < this.termCount) {
String sTerm = this.terms[this.currentIndex];
this.termAttribute.setTermBuffer(sTerm.toLowerCase());
return true;
}
return false;
}
@Override
public void reset() throws IOException {
this.currentIndex = -1;
}
}
}