/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.internal; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.HashMap; import org.zoodb.api.impl.ZooPC; import org.zoodb.internal.SerializerTools.PRIMITIVE; import org.zoodb.internal.server.index.BitTools; import org.zoodb.internal.util.DBLogger; public class ZooFieldDef { public static final int OFS_INIITIAL = 8; //OID public static enum JdoType { PRIMITIVE(-1, true), //Numbers are like SCOs. They cannot be indexable, because they can be 'null'! //Furthermore, if the type is Number, then it could be everything from boolean to double. NUMBER(0, false), REFERENCE(1+1 + 8 + 8, true), STRING(1+8, true), DATE(1+8, true), BIG_INT(0, false), BIG_DEC(0, false), ARRAY(0, false), SCO(0, false); private final byte len; private final boolean fixedSize; JdoType(int len, boolean fixedSize) { this.len = (byte) len; this.fixedSize = fixedSize; } byte getLen() { return len; } } private String fName; private final long schemaId; private final String typeName; private long typeOid; private final int arrayDim; private transient ZooClassDef typeDef; private transient Class<?> javaTypeDef; private transient Field javaField; private final ZooClassDef declaringType; private JdoType jdoType; private boolean isIndexed = false;; private boolean isIndexUnique; private int offset = Integer.MIN_VALUE; private int fieldPos = -1; private final byte fieldLength; private final boolean isFixedSize; private final PRIMITIVE primitive; private transient ZooFieldProxy proxy = null; private static final HashMap<String, Integer> PRIMITIVES = new HashMap<String, Integer>(); static { PRIMITIVES.put(Boolean.TYPE.getName(), 1); PRIMITIVES.put(Byte.TYPE.getName(), 1); PRIMITIVES.put(Character.TYPE.getName(), 2); PRIMITIVES.put(Double.TYPE.getName(), 8); PRIMITIVES.put(Float.TYPE.getName(), 4); PRIMITIVES.put(Integer.TYPE.getName(), 4); PRIMITIVES.put(Long.TYPE.getName(), 8); PRIMITIVES.put(Short.TYPE.getName(), 2); PRIMITIVES.put(Boolean.class.getName(), 1); PRIMITIVES.put(Byte.class.getName(), 1); PRIMITIVES.put(Character.class.getName(), 2); PRIMITIVES.put(Double.class.getName(), 8); PRIMITIVES.put(Float.class.getName(), 4); PRIMITIVES.put(Integer.class.getName(), 4); PRIMITIVES.put(Long.class.getName(), 8); PRIMITIVES.put(Short.class.getName(), 2); PRIMITIVES.put(BigInteger.class.getName(), 0); PRIMITIVES.put(BigDecimal.class.getName(), 0); PRIMITIVES.put(Date.class.getName(), 8); PRIMITIVES.put(String.class.getName(), 8); PRIMITIVES.put(ZooPC.class.getName(), 1 + 8 + 8); } @SuppressWarnings("unused") private ZooFieldDef () { //private constructor for de-serializer only! typeName = null; primitive = null; isFixedSize = false; fieldLength = 0; fName = null; declaringType = null; schemaId = -1; arrayDim = 0; } /** * Copy constructor. */ ZooFieldDef (ZooFieldDef f, ZooClassDef declaringClass) { //private constructor for de-serializer only! fName = f.fName; typeName = f.typeName; primitive = f.primitive; isFixedSize = f.isFixedSize; fieldLength = f.fieldLength; declaringType = declaringClass; typeOid = f.typeOid; typeDef = f.typeDef; javaTypeDef = f.javaTypeDef; javaField = f.javaField; jdoType = f.jdoType; isIndexed = f.isIndexed; isIndexUnique = f.isIndexUnique; offset = f.offset; fieldPos = f.fieldPos; proxy = f.proxy; schemaId = f.schemaId; arrayDim = f.arrayDim; } ZooFieldDef(ZooClassDef declaringType, String name, String typeName, int arrayDim, JdoType jdoType, long oid) { this.declaringType = declaringType; this.fName = name; this.typeName = typeName; this.jdoType = jdoType; this.arrayDim = arrayDim; //This is not a new version but a new field, so the schemId equals the OID. //TODO remove this in case Field becomes a PC. Then use the actual OID this.schemaId = oid; // if (_isPrimitive) { // _fieldLength = (byte)(int) PRIMITIVES.get(typeName); // } else if(_isString) { // _fieldLength = 8; //magic number for indexing (4 ASCII chars + 4 byte hash) // } else if(_isArray) { // _fieldLength = 4; //full array length (serialized form incl. sub-arrays) // } else if(_isPersistent) { // _fieldLength = 1 + 8 + 8; //type byte + Schema-OID + OID // } else { // //SCO // _fieldLength = 1; //The rest is stored in the appendix. // } if (this.jdoType == JdoType.PRIMITIVE) { fieldLength = (byte)(int)PRIMITIVES.get(typeName); PRIMITIVE prim = getPrimitiveType(typeName); //_primitive = SerializerTools.PRIMITIVE_TYPES.get(Class.forName(typeName)); if ((primitive = prim) == null) { throw new RuntimeException("Primitive type not found: " + typeName); } } else { this.fieldLength = this.jdoType.getLen(); this.primitive = null; } this.isFixedSize = this.jdoType.fixedSize; } private PRIMITIVE getPrimitiveType(String typeName) { for (PRIMITIVE p: PRIMITIVE.values()) { if (p.name().equals(typeName.toUpperCase())) { return p; } } return null; } public static ZooFieldDef createFromJavaType(ZooClassDef declaringType, Field jField, long fieldOid) { Class<?> fieldType = jField.getType(); //// //TODO does this return true for primitive arrays? //// boolean isPrimitive = PRIMITIVES.containsKey(fieldType.getName()); //// //TODO store dimension instead? //// boolean isArray = fieldType.isArray(); //// boolean isString = String.class.equals(fieldType); //// //TODO does this return true for arrays? //// boolean isPersistent = PersistenceCapableImpl.class.isAssignableFrom(fieldType); // ZooFieldDef f = new ZooFieldDef(declaringType, jField.getName(), fieldType.getName(), // jdoType); //// isPrimitive, isArray, isString, isPersistent); ZooFieldDef f = create(declaringType,jField.getName(), fieldType, fieldOid); f.setJavaField(jField); return f; } public static ZooFieldDef create(ZooClassDef declaringType, String fieldName, Class<?> fieldType, long fieldOid) { if (ObjectGraphTraverser.ILLEGAL_TYPES.contains(fieldType)) { throw DBLogger.newUser("Class fields of this type cannot be stored. Could they be " + "made 'static' or 'transient'? Type: " + fieldType + " in " + declaringType.getClassName() + "." + fieldName); } String typeName = fieldType.getName(); JdoType jdoType = getJdoType(fieldType); int arrayDim = 0; for (int i = 0; i < typeName.length(); i++) { if (typeName.charAt(i) == '[') { arrayDim++; } } ZooFieldDef f = new ZooFieldDef(declaringType, fieldName, typeName, arrayDim, jdoType, fieldOid); return f; } private static JdoType getJdoType(Class<?> fieldType) { JdoType jdoType; if (fieldType.isArray()) { jdoType = JdoType.ARRAY; } else if (fieldType.isPrimitive()) { jdoType = JdoType.PRIMITIVE; } else if (String.class.equals(fieldType)) { jdoType = JdoType.STRING; } else if (ZooPC.class.isAssignableFrom(fieldType)) { jdoType = JdoType.REFERENCE; } else if (Date.class.equals(fieldType)) { jdoType = JdoType.DATE; } else if (BigInteger.class.equals(fieldType)) { jdoType = JdoType.BIG_INT; } else if (BigDecimal.class.equals(fieldType)) { jdoType = JdoType.BIG_DEC; } else if (Number.class.isAssignableFrom(fieldType)) { jdoType = JdoType.NUMBER; } else { jdoType = JdoType.SCO; } return jdoType; } /** * Creates references and reference arrays to persistent classes. * @param declaringType * @param fieldName * @param fieldType The ZooCLassDef of the target class of a reference. * @param arrayDim * @return ZooFieldDef */ public static ZooFieldDef create(ZooClassDef declaringType, String fieldName, ZooClassDef fieldType, int arrayDim) { String typeName = fieldType.getClassName(); JdoType jdoType; if (arrayDim > 0) { jdoType = JdoType.ARRAY; } else { jdoType = JdoType.REFERENCE; } long fieldOid = declaringType.jdoZooGetNode().getOidBuffer().allocateOid(); ZooFieldDef f = new ZooFieldDef(declaringType, fieldName, typeName, arrayDim, jdoType, fieldOid); return f; } public PRIMITIVE getPrimitiveType() { return primitive; } public boolean isPrimitiveType() { return jdoType == JdoType.PRIMITIVE; } public boolean isPersistentType() { return jdoType == JdoType.REFERENCE; } public void setType(ZooClassDef clsDef) { this.typeDef = clsDef; this.typeOid = clsDef.getOid(); } public ZooClassDef getType() { return typeDef; } public String getName() { return fName; } public String getTypeName() { return typeName; } public boolean isIndexed() { return isIndexed; } public boolean isIndexUnique() { return isIndexUnique; } public void setIndexed(boolean b) { isIndexed = b; } public void setUnique(boolean isUnique) { isIndexUnique = isUnique; } protected int getNextOffset() { return offset + fieldLength; } public int getOffset() { return offset; } /** * * @return The position of the field. This counts from [0.. nFields-1]. */ public int getFieldPos() { return fieldPos; } public Class<?> getJavaType() { return javaTypeDef; } public Field getJavaField() { return javaField; } public long getTypeOID() { return typeOid; } public boolean isArray() { //TODO buffer in booleans return jdoType == JdoType.ARRAY; } public boolean isString() { return jdoType == JdoType.STRING; } public void setOffset(int ofs, int fieldPos) { this.offset = ofs; this.fieldPos = fieldPos; } public void setJavaField(Field javaField) { checkField(javaField); this.javaField = javaField; this.javaTypeDef = javaField.getType(); this.javaField.setAccessible(true); } private void checkField(Field javaField) { if (!javaField.getName().equals(fName)) { throw DBLogger.newUser( "Field name mismatch: " + fName + " <-> " + javaField.getName()); } JdoType jdoType = getJdoType(javaField.getType()); if (jdoType != this.jdoType) { throw DBLogger.newUser("Field type mismatch: " + this.jdoType + " <-> " + jdoType); } if (jdoType == JdoType.PRIMITIVE) { PRIMITIVE prim = getPrimitiveType(javaField.getType().getName()); if (prim != this.primitive) { throw DBLogger.newUser("Field type mismatch: " + this.primitive + " <-> " + prim); } } } public void unsetJavaField() { this.javaField = null; this.javaTypeDef = null; } public JdoType getJdoType() { return jdoType; } public int getLength() { return fieldLength; } public boolean isFixedSize() { return isFixedSize; } public boolean isDate() { return jdoType == JdoType.DATE; } public long getMinValue() { if (isPrimitiveType()) { switch(getPrimitiveType()) { case BOOLEAN: return 0; case BYTE: return Byte.MIN_VALUE; case CHAR: return Character.MIN_VALUE; case DOUBLE: return BitTools.toSortableLong(Double.NEGATIVE_INFINITY); case FLOAT: return BitTools.toSortableLong(Float.NEGATIVE_INFINITY); case INT: return Integer.MIN_VALUE; case LONG: return Long.MIN_VALUE; case SHORT: return Short.MIN_VALUE; } } if (isString() || isPersistentType()) { return Long.MIN_VALUE; //TODO is this correct? Can it be negative? } if (isDate()) { return 0; //TODO is this correct? } throw DBLogger.newUser("Type not supported in query: " + typeName); } public long getMaxValue() { if (isPrimitiveType()) { switch(getPrimitiveType()) { case BOOLEAN: return 0; case BYTE: return Byte.MAX_VALUE; case CHAR: return Character.MAX_VALUE; case DOUBLE: return BitTools.toSortableLong(Double.POSITIVE_INFINITY); case FLOAT: return BitTools.toSortableLong(Float.POSITIVE_INFINITY); case INT: return Integer.MAX_VALUE; case LONG: return Long.MAX_VALUE; case SHORT: return Short.MAX_VALUE; } } if (isString() || isPersistentType()) { return Long.MAX_VALUE; } if (isDate()) { return Long.MAX_VALUE; //TODO is this correct? } throw DBLogger.newUser("Type not supported in query: " + typeName); } public ZooClassDef getDeclaringType() { return declaringType; } public ZooFieldProxy getProxy() { if (proxy == null) { proxy = new ZooFieldProxy(this, declaringType.jdoZooGetContext().getSession().getSchemaManager()); } return proxy; } @Override public String toString() { return "Field: " + declaringType.getClassName() + "." + fName; } public void updateName(String fieldName) { this.fName = fieldName; declaringType.rebuildFieldsRecursive(); } /** * * @return The unique ID of this field, which stays the same for different versions of the * field. */ public long getFieldSchemaId() { return schemaId; } public int getArrayDim() { return arrayDim; } }