org.openmdx.base.wbxml.DynamicPlugIn Maven / Gradle / Ivy
/*
* ====================================================================
* Project: openMDX/Core, http://www.openmdx.org/
* Description: Dynamic Plug-In
* Owner: OMEX AG, Switzerland, http://www.omex.ch
* ====================================================================
*
* This software is published under the BSD license as listed below.
*
* Copyright (c) 2013, OMEX AG, Switzerland All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the openMDX team nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* ------------------
*
* This product includes software developed by other organizations as listed in
* the NOTICE file.
*/
package org.openmdx.base.wbxml;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.openmdx.base.exception.ServiceException;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.exception.Throwables;
import org.openmdx.kernel.log.SysLog;
/**
* The dynamic plug-in is able to amend its tables:
* ext0()
calls amend the code tables
* ext1()
calls amend the string table
*
*/
public class DynamicPlugIn extends AbstractPlugIn {
/**
* Constructor
*
* @param page0 the (usually static) page 0
*/
protected DynamicPlugIn(
Page page0
) {
this.page0 = page0;
}
/**
* Page 0 is usually static
*/
private final Page page0;
/**
* Pages 1.. 254
*/
private final Page[] pages = new Page[254];
/**
* The target is set by an extT0() invocation
*/
private CodeToken codeTarget;
/**
* The target is set by an ext1() invocation
*/
private int stringTarget = -1;
/**
* The dynamic string table size in case of UTF-16 encoding
*/
private int stringSinkSize = 0;
/**
* The dynamic string sink
*/
private final Map stringSink = new HashMap();
/**
* The dynamic string sink
*/
private final Map stringSource = new HashMap();
/**
* The dynamic plug-in's initial string table is always empty!
*/
private static ByteBuffer stringTable = ByteBuffer.wrap(new byte[]{});
/**
* Page factory method
*
* @return a new page
*/
protected static Page newPage() {
return new Page();
}
/**
* Register values for page 0
*
* @param page0 the target page
* @param codeSpace the code space the value belongs to
* @param value the value to be registered
* @param lenient if true
then collisions are logged as warnings,
* otherwise they lead to exceptions.
*/
protected static void addTo(Page page0, CodeSpace codeSpace, String value, boolean lenient) {
final int index = getIndex(value);
final String[] target = page0.get(codeSpace);
final Integer codePage = Integer.valueOf(0);
final Integer codePoint = Integer.valueOf(index);
if (target[index] == null) {
target[index] = value;
SysLog.log(
Level.FINE,
"Value \"{0}\" is mapped to [{1}, {2}, {3}]",
value,
codeSpace,
codePage,
codePoint
);
} else if (!value.equals(target[index])) {
if(lenient){
SysLog.log(
Level.WARNING,
"Both values \"{0}\" and \"{4}\" are mapped to [{1}, {2}, {3}]]",
value,
codeSpace,
codePage,
codePoint,
target[index]
);
} else {
throw Throwables.initCause(
new IllegalArgumentException(
"There are two values which can't be put into the same dynamically built code page"
),
null, // exception
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.DUPLICATE,
new BasicException.Parameter("code space", codeSpace),
new BasicException.Parameter("code page", codePage),
new BasicException.Parameter("code point", codePoint),
new BasicException.Parameter("conflicting values", value, target[index])
);
}
}
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.wbxml.AbstractPlugIn#reset()
*/
@Override
public void reset() {
super.reset();
for (Page page : pages) {
if(page == null) break;
page.clear();
}
this.codeTarget = null;
this.stringTarget = -1;
this.stringSinkSize = 0;
this.stringSink.clear();
this.stringSource.clear();
super.reset();
}
/* (non-Javadoc)
* @see org.openmdx.base.wbxml.AbstractPlugIn#getStringTable()
*/
@Override
public ByteBuffer getStringTable(
) throws ServiceException {
return stringTable;
}
private static final int getIndex(
String value
){
return Math.abs(value.hashCode() % Page.SIZE);
}
/**
* Retrieve an exact-match token
*
* @param codeSpace
* @param readOnly
* @param value
*
* @return the corresponding token, or null
if no such entry is
* available
*/
private CodeToken getExactMatch(
CodeSpace codeSpace,
String value
) {
int index = getIndex(value);
if(value.equals(this.page0.get(codeSpace)[index])) {
return codeSpace.newToken(
0,
index,
value.length(),
false
); // pre-existing entry
}
for(
int page = 1;
page <= this.pages.length;
page++
){
Page lazilyAllocatedPage = this.pages[page-1];
if(lazilyAllocatedPage == null) {
lazilyAllocatedPage = this.pages[page-1] = newPage();
}
String[] sector = lazilyAllocatedPage.get(codeSpace);
String entry = sector[index];
if (entry == null) {
sector[index] = value;
return codeSpace.newToken(
page,
index,
value.length(),
true // newly created entry
);
}
if (entry.equals(value)) {
return codeSpace.newToken(
page,
index,
value.length(),
false // pre-existing entry
);
}
}
return null; // pages exhausted
}
/**
* Retrieve a best-match token
*
* @param codeSpace
* @param readOnly
* @param value
*
* @return the corresponding token, or null
if no such entry is
* available
*/
private CodeToken getBestMatch(
CodeSpace codeSpace,
boolean set,
String value
) {
int bestPage = -1; // page of best match
int bestIndex = -1; // index of best match
int bestLength = -1; // length of best match
int page = 0;
Pages: for (
Page currentPage = this.page0;
page < pages.length;
currentPage = pages[page++]
) {
if (currentPage == null) {
if (!set) return null;
currentPage = pages[page - 1] = newPage(); // create a new page
// on demand
}
String[] sector = currentPage.get(codeSpace);
int index = 0;
for (String entry : sector) {
if (entry == null) {
if (page == 0) continue Pages;
if (!set) break Pages;
sector[index] = value;
return codeSpace.newToken(
page,
index,
value.length(),
true // newly created entry
);
}
if (entry.equals(value)) {
return codeSpace.newToken(
page,
index,
value.length(),
false // pre-existing entry
);
}
if (entry.length() > bestLength && value.startsWith(entry)) {
bestPage = page;
bestIndex = index;
bestLength = entry.length();
}
index++;
}
}
return bestLength > 0 ? codeSpace.newToken(
bestPage,
bestIndex,
bestLength,
false
) : null;
}
/* (non-Javadoc)
* @see org.openmdx.base.wbxml.PlugIn#findStringToken(java.lang.String)
*/
@Override
public StringToken findStringToken(String value) {
Integer oldIndex = this.stringSink.get(value);
return oldIndex == null ? null : new StringToken(oldIndex.intValue(), false);
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getStringToken(java.lang.String)
*/
@Override
public StringToken getStringToken(String value) {
StringToken token = findStringToken(value);
if(token == null) {
int newIndex = this.stringSinkSize;
this.stringSinkSize += 2 * (value.length() + 1);
return new StringToken(newIndex, true);
} else {
return token;
}
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getTagToken(java.lang.String
* )
*/
@Override
public CodeToken getTagToken(String namespaceURI, String value) {
return this.getExactMatch(CodeSpace.TAG, value);
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getAttributeNameToken(java
* .lang.String)
*/
@Override
public CodeToken getAttributeNameToken(
String namespaceURI,
String elementName,
String attributeName
) {
return getExactMatch(CodeSpace.ATTRIBUTE_NAME, attributeName);
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getAttributeValueToken(
* java.lang.String)
*/
@Override
public CodeToken getAttributeValueToken(String namespaceURI, String value) {
return getExactMatch(CodeSpace.ATTRIBUTE_VALUE, value);
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getAttributeStartToken(
* boolean, java.lang.String, java.lang.String)
*/
@Override
public CodeToken findAttributeStartToken(
boolean force,
String namespaceURI,
String elementName,
String attributeName,
String value
) {
CodeToken token = getBestMatch(
CodeSpace.ATTRIBUTE_NAME_WITH_VALUE_PREFIX,
force,
attributeName + '=' + value
);
return token == null ? getExactMatch(CodeSpace.ATTRIBUTE_NAME, attributeName) : token;
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#getAttributeValueStartToken
* (boolean, java.lang.String)
*/
@Override
public CodeToken findAttributeValueToken(boolean force, String namespaceURI, String value) {
return getBestMatch(CodeSpace.ATTRIBUTE_VALUE_PREFIX, force, value);
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#ext0(int)
*/
@Override
public void ext0(
int argument
) throws ServiceException {
CodeToken pending = this.codeTarget;
this.codeTarget = new CodeToken(argument);
if (pending != null) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"An ext0(int) invocation must immediately be followed by an ext0(java.lang.String) invocation",
new BasicException.Parameter("new-token", this.codeTarget),
new BasicException.Parameter("pending-token", pending)
);
}
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#ext0(java.lang.String)
*/
@Override
public void ext0(
String argument
) throws ServiceException {
//
// Validate the plug-in's state
//
CodeToken token = this.codeTarget;
if (token == null) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"An ext0(int) invocation must immediately be preceeded by an ext0(java.lamg.String) invocation",
new BasicException.Parameter("token"),
new BasicException.Parameter("value", argument)
);
} else {
this.codeTarget = null;
}
//
// Provide the page
//
int pageIndex = token.getPage() - 1;
Page target = pages[pageIndex];
if (target == null) {
target = pages[pageIndex] = newPage();
}
//
// Provide the sector
//
CodeSpace codeSpace = CodeSpace.ofToken(token);
String[] sector = target.get(codeSpace);
//
// Set the value
//
int codeIndex = (token.getCode() & 0x3f) - 5;
if (sector[codeIndex] == null) {
sector[codeIndex] = argument;
} else if (!sector[codeIndex].equals(argument)) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"Token/value conflict",
new BasicException.Parameter("token", token),
new BasicException.Parameter("old-value", sector[codeIndex]),
new BasicException.Parameter("new-value", argument)
);
}
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#ext1(int)
*/
@Override
public void ext1(int argument)
throws ServiceException {
int pending = this.stringTarget;
this.stringTarget = argument;
if (pending != -1){
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"An ext0() invocation must immediately be followed by an ext1(java.lang.String) invocation",
new BasicException.Parameter("new-token", this.stringTarget),
new BasicException.Parameter("pending-token", pending)
);
}
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#ext1(java.lang.String)
*/
@Override
public void ext1(String argument)
throws ServiceException {
//
// Validate the plug-in's state
//
int token = this.stringTarget;
if (token < 0) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"An ext1(int) invocation must immediately be preceeded by an ext1(java.lang.String) invocation",
new BasicException.Parameter("token"),
new BasicException.Parameter("value", argument));
}
String value = this.stringSource.get(Integer.valueOf(token));
this.stringTarget = -1;
if (value != null && !value.equals(argument)) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.ILLEGAL_STATE,
"Token/value conflict",
new BasicException.Parameter("token", token),
new BasicException.Parameter("old-value", value),
new BasicException.Parameter("new-value", argument));
}
}
/**
* Retrieve the given attribute value
*
* @param codeSpace
* @param page
* @param index
*
* @return the requested value
*
* @throws ServiceException
*/
private String getValue(
CodeSpace codeSpace,
int page,
int code
) throws ServiceException {
String[] values = (
page == 0 ? this.page0 : this.pages[page - 1]
).get(
codeSpace
);
int index = (code & 0x3f) - 5;
String value = values[index];
if (value == null) {
throw new ServiceException(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.BAD_PARAMETER,
"The given token is undefined",
new BasicException.Parameter("space", codeSpace),
new BasicException.Parameter("page", page),
new BasicException.Parameter("index", index)
);
}
return value;
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#resolveString(int)
*/
@Override
public String resolveString(
int index
) throws ServiceException {
String value = this.stringSource.get(Integer.valueOf(index));
return value == null ? super.resolveString(index) : value;
}
/*
* (non-Javadoc)
*
* @see
* org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#resolveAttributeValue(int,
* int)
*/
@Override
public CodeResolution resolveAttributeStart(
int page,
int code
) throws ServiceException {
String entry = getValue(CodeSpace.ofAttributeCode(code), page, code);
int separator = entry.indexOf('=');
if(separator < 0){
return new CodeResolution(
"", // namespaceURI
"", // localName
entry, // qName
"" // valueStart
);
} else {
return new CodeResolution(
"", // namespaceURI
"", // localName
entry.substring(0, separator), // qName
entry.substring(separator + 1) // valueStart
);
}
}
/*
* (non-Javadoc)
*
* @see org.openmdx.base.xml.wbxml.spi.AbstractPlugIn#resolveTag(int, int)
*/
@Override
public Object resolveTag(
int page,
int code
) throws ServiceException {
return getValue(CodeSpace.TAG, page, code);
}
/**
* Tells whether a given code is an attribute value code
*
* @param code
*
* @return false
in case of an exact-match code
*/
static boolean hasAttributeValueFlag(int code) {
return (code & 0x80) != 0;
}
/**
* Tells whether a given code is a best-match code
*
* @param code
*
* @return false
in case of an exact-match code
*/
static boolean hasStartsWithFlag(int code) {
return (code & 0x40) != 0;
}
// ------------------------------------------------------------------------
// Class CodeSpace
// ------------------------------------------------------------------------
/**
* Code Space
*/
protected static enum CodeSpace {
ATTRIBUTE_NAME(0x10000),
ATTRIBUTE_NAME_WITH_VALUE_PREFIX(0x10040),
ATTRIBUTE_VALUE(0x10080),
ATTRIBUTE_VALUE_PREFIX(0x100C0),
TAG(0x00000);
/**
* Constructor
*
* @param pattern
*/
CodeSpace(int pattern) {
this.pattern = pattern;
}
/**
* The pattern applied to the token
*/
final int pattern;
/**
* Retrieve the code space a token belongs to
*
* @param token
*
* @return the code space a token belongs to
*/
static CodeSpace ofToken(CodeToken token) {
return token.isAttributeCodeSpace() ? ofAttributeCode(token.getCode()) : TAG;
}
/**
* Retrieve the code space an an attribute code belongs to
*
* @param code
*
* @return the code space an an attribute code belongs to
*/
static CodeSpace ofAttributeCode(int code) {
return hasAttributeValueFlag(code) ? (
hasStartsWithFlag(code) ? CodeSpace.ATTRIBUTE_VALUE_PREFIX : CodeSpace.ATTRIBUTE_VALUE
) : (
hasStartsWithFlag(code) ? CodeSpace.ATTRIBUTE_NAME_WITH_VALUE_PREFIX : CodeSpace.ATTRIBUTE_NAME
);
}
/**
* Get a token for the given page and index
*
* @param page
* @param index
* @param length
* the value's length
* @param created
* true
if the entry has been created on the fly
*
* @return the corresponding token
*/
final CodeToken newToken(
int page,
int index,
int length,
boolean created
) {
return new CodeToken(
page << 8 | index + 5 | this.pattern,
length,
created
);
}
}
// ------------------------------------------------------------------------
// Class Page
// ------------------------------------------------------------------------
/**
* Page
*/
protected final static class Page {
/**
* Slots per page
*/
static final int SIZE = 59;
/**
* Clear the slots but retain the allocated memory
*/
final void clear() {
Arrays.fill(this.tag, null);
Arrays.fill(this.attributeName, null);
Arrays.fill(this.attributeNameWithValuePrefix, null);
Arrays.fill(this.attributeValue, null);
Arrays.fill(this.attributeValuePrefix, null);
}
final String[] get(CodeSpace codeSpace) {
switch (codeSpace) {
case TAG:
return this.tag;
case ATTRIBUTE_NAME:
return this.attributeName;
case ATTRIBUTE_NAME_WITH_VALUE_PREFIX:
return this.attributeNameWithValuePrefix;
case ATTRIBUTE_VALUE:
return this.attributeValue;
case ATTRIBUTE_VALUE_PREFIX:
return this.attributeValuePrefix;
default:
return null;
}
}
/**
* Tags
*/
private final String[] tag = new String[SIZE];
/**
* Attribute name without value
*/
private final String[] attributeName = new String[SIZE];
/**
* Attribute name with value prefix
*/
private final String[] attributeNameWithValuePrefix = new String[SIZE];
/**
* Complete attribute value
*/
private final String[] attributeValue = new String[SIZE];
/**
* Attribute value prefix
*/
private final String[] attributeValuePrefix = new String[SIZE];
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy