| /** |
| * 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.apache.gora.persistency.impl; |
| |
| import java.nio.ByteBuffer; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.avro.Schema.Field; |
| import org.apache.avro.Schema.Type; |
| import org.apache.avro.specific.SpecificRecord; |
| import org.apache.gora.avro.PersistentDatumReader; |
| import org.apache.gora.persistency.ListGenericArray; |
| import org.apache.gora.persistency.Persistent; |
| import org.apache.gora.persistency.StateManager; |
| import org.apache.gora.persistency.StatefulHashMap; |
| |
| /** |
| * Base classs implementing common functionality for Persistent |
| * classes. |
| */ |
| public abstract class PersistentBase implements Persistent { |
| |
| protected static Map<Class<?>, Map<String, Integer>> FIELD_MAP = |
| new HashMap<Class<?>, Map<String,Integer>>(); |
| |
| protected static Map<Class<?>, String[]> FIELDS = |
| new HashMap<Class<?>, String[]>(); |
| |
| protected static final PersistentDatumReader<Persistent> datumReader = |
| new PersistentDatumReader<Persistent>(); |
| |
| private StateManager stateManager; |
| |
| protected PersistentBase() { |
| this(new StateManagerImpl()); |
| } |
| |
| protected PersistentBase(StateManager stateManager) { |
| this.stateManager = stateManager; |
| stateManager.setManagedPersistent(this); |
| } |
| |
| /** Subclasses should call this function for all the persistable fields |
| * in the class to register them. |
| * @param clazz the Persistent class |
| * @param fields the name of the fields of the class |
| */ |
| protected static void registerFields(Class<?> clazz, String... fields) { |
| FIELDS.put(clazz, fields); |
| int fieldsLength = fields == null ? 0 :fields.length; |
| HashMap<String, Integer> map = new HashMap<String, Integer>(fieldsLength); |
| |
| for(int i=0; i < fieldsLength; i++) { |
| map.put(fields[i], i); |
| } |
| FIELD_MAP.put(clazz, map); |
| } |
| |
| @Override |
| public StateManager getStateManager() { |
| return stateManager; |
| } |
| |
| @Override |
| public String[] getFields() { |
| return FIELDS.get(getClass()); |
| } |
| |
| @Override |
| public String getField(int index) { |
| return FIELDS.get(getClass())[index]; |
| } |
| |
| @Override |
| public int getFieldIndex(String field) { |
| return FIELD_MAP.get(getClass()).get(field); |
| } |
| |
| @Override |
| @SuppressWarnings("rawtypes") |
| public void clear() { |
| List<Field> fields = getSchema().getFields(); |
| |
| for(int i=0; i<getFields().length; i++) { |
| switch(fields.get(i).schema().getType()) { |
| case MAP: |
| if(get(i) != null) { |
| if (get(i) instanceof StatefulHashMap) { |
| ((StatefulHashMap)get(i)).reuse(); |
| } else { |
| ((Map)get(i)).clear(); |
| } |
| } |
| break; |
| case ARRAY: |
| if(get(i) != null) { |
| if(get(i) instanceof ListGenericArray) { |
| ((ListGenericArray)get(i)).clear(); |
| } else { |
| put(i, new ListGenericArray(fields.get(i).schema())); |
| } |
| } |
| break; |
| case RECORD : |
| Persistent field = ((Persistent)get(i)); |
| if(field != null) field.clear(); |
| break; |
| case BOOLEAN: put(i, false); break; |
| case INT : put(i, 0); break; |
| case DOUBLE : put(i, 0d); break; |
| case FLOAT : put(i, 0f); break; |
| case LONG : put(i, 0l); break; |
| case NULL : break; |
| default : put(i, null); break; |
| } |
| } |
| clearDirty(); |
| clearReadable(); |
| } |
| |
| @Override |
| public boolean isNew() { |
| return getStateManager().isNew(this); |
| } |
| |
| @Override |
| public void setNew() { |
| getStateManager().setNew(this); |
| } |
| |
| @Override |
| public void clearNew() { |
| getStateManager().clearNew(this); |
| } |
| |
| @Override |
| public boolean isDirty() { |
| return getStateManager().isDirty(this); |
| } |
| |
| @Override |
| public boolean isDirty(int fieldIndex) { |
| return getStateManager().isDirty(this, fieldIndex); |
| } |
| |
| @Override |
| public boolean isDirty(String field) { |
| return isDirty(getFieldIndex(field)); |
| } |
| |
| @Override |
| public void setDirty() { |
| getStateManager().setDirty(this); |
| } |
| |
| @Override |
| public void setDirty(int fieldIndex) { |
| getStateManager().setDirty(this, fieldIndex); |
| } |
| |
| @Override |
| public void setDirty(String field) { |
| setDirty(getFieldIndex(field)); |
| } |
| |
| @Override |
| public void clearDirty(int fieldIndex) { |
| getStateManager().clearDirty(this, fieldIndex); |
| } |
| |
| @Override |
| public void clearDirty(String field) { |
| clearDirty(getFieldIndex(field)); |
| } |
| |
| @Override |
| public void clearDirty() { |
| getStateManager().clearDirty(this); |
| } |
| |
| @Override |
| public boolean isReadable(int fieldIndex) { |
| return getStateManager().isReadable(this, fieldIndex); |
| } |
| |
| @Override |
| public boolean isReadable(String field) { |
| return isReadable(getFieldIndex(field)); |
| } |
| |
| @Override |
| public void setReadable(int fieldIndex) { |
| getStateManager().setReadable(this, fieldIndex); |
| } |
| |
| @Override |
| public void setReadable(String field) { |
| setReadable(getFieldIndex(field)); |
| } |
| |
| @Override |
| public void clearReadable() { |
| getStateManager().clearReadable(this); |
| } |
| |
| @Override |
| public void clearReadable(int fieldIndex) { |
| getStateManager().clearReadable(this, fieldIndex); |
| } |
| |
| @Override |
| public void clearReadable(String field) { |
| clearReadable(getFieldIndex(field)); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof SpecificRecord)) return false; |
| |
| SpecificRecord r2 = (SpecificRecord)o; |
| if (!this.getSchema().equals(r2.getSchema())) return false; |
| |
| return this.hashCode() == r2.hashCode(); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| List<Field> fields = this.getSchema().getFields(); |
| int end = fields.size(); |
| for (int i = 0; i < end; i++) { |
| result = prime * result + getFieldHashCode(i, fields.get(i)); |
| } |
| return result; |
| } |
| |
| private int getFieldHashCode(int i, Field field) { |
| Object o = get(i); |
| if(o == null) |
| return 0; |
| |
| if(field.schema().getType() == Type.BYTES) { |
| return getByteBufferHashCode((ByteBuffer)o); |
| } |
| |
| return o.hashCode(); |
| } |
| |
| /** ByteBuffer.hashCode() takes into account the position of the |
| * buffer, but we do not want that*/ |
| private int getByteBufferHashCode(ByteBuffer buf) { |
| int h = 1; |
| int p = buf.arrayOffset(); |
| for (int j = buf.limit() - 1; j >= p; j--) |
| h = 31 * h + buf.get(j); |
| return h; |
| } |
| |
| @Override |
| public Persistent clone() { |
| return datumReader.clone(this, getSchema()); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(super.toString()); |
| builder.append(" {\n"); |
| List<Field> fields = getSchema().getFields(); |
| for(int i=0; i<fields.size(); i++) { |
| builder.append(" \"").append(fields.get(i).name()).append("\":\""); |
| builder.append(get(i)).append("\"\n"); |
| } |
| builder.append("}"); |
| return builder.toString(); |
| } |
| |
| protected boolean isFieldEqual(int index, Object value) { |
| Object old = get(index); |
| if (old == null && value == null) |
| return true; |
| if (old == null || value == null) |
| return false; |
| return value.equals(old); |
| } |
| } |