Home | History | Annotate | Download | only in memory
      1 /*
      2  * Copyright (C) 2012 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.caliper.memory;
     18 
     19 import com.google.common.base.Preconditions;
     20 
     21 import java.lang.reflect.Field;
     22 import java.util.ArrayDeque;
     23 import java.util.Deque;
     24 import java.util.Iterator;
     25 
     26 import javax.annotation.Nonnull;
     27 import javax.annotation.Nullable;
     28 
     29 /**
     30  * A chain of references, which starts at a root object and leads to a
     31  * particular value (either an object or a primitive).
     32  */
     33 public abstract class Chain {
     34   private final Object value;
     35   private final Chain parent;
     36 
     37   Chain(Chain parent, Object value) {
     38     this.parent = parent;
     39     this.value = value;
     40   }
     41 
     42   static Chain root(Object value) {
     43     return new Chain(null, Preconditions.checkNotNull(value)) {
     44       @Override
     45       public Class<?> getValueType() {
     46         return getValue().getClass();
     47       }
     48     };
     49   }
     50 
     51   FieldChain appendField(Field field, Object value) {
     52     return new FieldChain(this, Preconditions.checkNotNull(field), value);
     53   }
     54 
     55   ArrayIndexChain appendArrayIndex(int arrayIndex, Object value) {
     56     return new ArrayIndexChain(this, arrayIndex, value);
     57   }
     58 
     59   /**
     60    * Returns whether this chain has a parent. This returns false only when
     61    * this chain represents the root object itself.
     62    */
     63   public boolean hasParent() {
     64     return parent != null;
     65   }
     66 
     67   /**
     68    * Returns the parent chain, from which this chain was created.
     69    * @throws IllegalStateException if {@code !hasParent()}, then an
     70    */
     71   public @Nonnull Chain getParent() {
     72     Preconditions.checkState(parent != null, "This is the root value, it has no parent");
     73     return parent;
     74   }
     75 
     76   /**
     77    * Returns the value that this chain leads to. If the value is a primitive,
     78    * a wrapper object is returned instead.
     79    */
     80   public @Nullable Object getValue() {
     81     return value;
     82   }
     83 
     84   public abstract @Nonnull Class<?> getValueType();
     85 
     86   /**
     87    * Returns whether the connection of the parent chain and this chain is
     88    * through a field (of the getParent().getValue().getClass() class).
     89    */
     90   public boolean isThroughField() {
     91     return false;
     92   }
     93 
     94   /**
     95    * Returns whether the connection of the parent chain and this chain is
     96    * through an array index, i.e. the parent leads to an array, and this
     97    * chain leads to an element of that array.
     98    */
     99   public boolean isThroughArrayIndex() {
    100     return false;
    101   }
    102 
    103   /**
    104    * Returns whether the value of this chain represents a primitive.
    105    */
    106   public boolean isPrimitive() {
    107     return getValueType().isPrimitive();
    108   }
    109 
    110   /**
    111    * Returns the root object of this chain.
    112    */
    113   public @Nonnull Object getRoot() {
    114     Chain current = this;
    115     while (current.hasParent()) {
    116       current = current.getParent();
    117     }
    118     return current.getValue();
    119   }
    120 
    121   Deque<Chain> reverse() {
    122     Deque<Chain> reverseChain = new ArrayDeque<Chain>(8);
    123     Chain current = this;
    124     reverseChain.addFirst(current);
    125     while (current.hasParent()) {
    126       current = current.getParent();
    127       reverseChain.addFirst(current);
    128     }
    129     return reverseChain;
    130   }
    131 
    132   @Override public final String toString() {
    133     StringBuilder sb = new StringBuilder(32);
    134 
    135     Iterator<Chain> it = reverse().iterator();
    136     sb.append(it.next().getValue());
    137     while (it.hasNext()) {
    138       sb.append("->");
    139       Chain current = it.next();
    140       if (current.isThroughField()) {
    141         sb.append(((FieldChain)current).getField().getName());
    142       } else if (current.isThroughArrayIndex()) {
    143         sb.append("[").append(((ArrayIndexChain)current).getArrayIndex()).append("]");
    144       }
    145     }
    146     return sb.toString();
    147   }
    148 
    149   static class FieldChain extends Chain {
    150     private final Field field;
    151 
    152     FieldChain(Chain parent, Field referringField, Object value) {
    153       super(parent, value);
    154       this.field = referringField;
    155     }
    156 
    157     @Override
    158     public boolean isThroughField() {
    159       return true;
    160     }
    161 
    162     @Override
    163     public boolean isThroughArrayIndex() {
    164       return false;
    165     }
    166 
    167     @Override
    168     public Class<?> getValueType() {
    169       return field.getType();
    170     }
    171 
    172     public Field getField() {
    173       return field;
    174     }
    175   }
    176 
    177   static class ArrayIndexChain extends Chain {
    178     private final int index;
    179 
    180     ArrayIndexChain(Chain parent, int index, Object value) {
    181       super(parent, value);
    182       this.index = index;
    183     }
    184 
    185     @Override
    186     public boolean isThroughField() {
    187       return false;
    188     }
    189 
    190     @Override
    191     public boolean isThroughArrayIndex() {
    192       return true;
    193     }
    194 
    195     @Override
    196     public Class<?> getValueType() {
    197       return getParent().getValue().getClass().getComponentType();
    198     }
    199 
    200     public int getArrayIndex() {
    201       return index;
    202     }
    203   }
    204 }
    205