/*
 * 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.geode.internal.offheap;

import org.apache.geode.internal.cache.CachedDeserializableFactory;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.offheap.annotations.Released;
import org.apache.geode.internal.offheap.annotations.Unretained;

/**
 * Utility class that provides static method to do some common tasks for off-heap references.
 *
 * @since Geode 1.0
 */
public class OffHeapHelper {
  private OffHeapHelper() {
    // no instances allowed
  }

  /**
   * If o is off-heap then return its heap form; otherwise return o since it is already on the heap.
   */
  public static Object getHeapForm(Object o) {
    if (o instanceof StoredObject) {
      return ((StoredObject) o).getValueAsDeserializedHeapObject();
    } else {
      return o;
    }
  }

  /**
   * Just like {@link #copyIfNeeded(Object, InternalCache)} except that if off-heap is copied it is
   * also released.
   *
   * @param v If this value is off-heap then the caller must have already retained it.
   * @return the heap copy to use in place of v
   */
  public static Object copyAndReleaseIfNeeded(@Released Object v, InternalCache cache) {
    if (v instanceof StoredObject) {
      @Unretained
      StoredObject ohv = (StoredObject) v;
      try {
        if (ohv.isSerialized()) {
          return CachedDeserializableFactory.create(ohv.getSerializedValue(), cache);
        } else {
          // it is a byte[]
          return ohv.getDeserializedForReading();
        }
      } finally {
        release(ohv);
      }
    } else {
      return v;
    }
  }

  /**
   * If v is on heap then just return v; no copy needed. Else v is off-heap so copy it to heap and
   * return a reference to the heap copy. Note that unlike {@link #getHeapForm(Object)} if v is a
   * serialized off-heap object it will be copied to the heap as a CachedDeserializable. If you
   * prefer to have the serialized object also deserialized and copied to the heap use
   * {@link #getHeapForm(Object)}.
   *
   * @param v possible OFF_HEAP_REFERENCE
   * @return v or a heap copy of v
   */
  public static Object copyIfNeeded(@Unretained Object v, InternalCache cache) {
    if (v instanceof StoredObject) {
      @Unretained
      StoredObject ohv = (StoredObject) v;
      if (ohv.isSerialized()) {
        v = CachedDeserializableFactory.create(ohv.getSerializedValue(), cache);
      } else {
        // it is a byte[]
        v = ohv.getDeserializedForReading();
      }
    }
    return v;
  }

  /**
   * @return true if release was done
   */
  public static boolean release(@Released Object o) {
    if (o instanceof StoredObject) {
      ((StoredObject) o).release();
      return true;
    } else {
      return false;
    }
  }

  /**
   * Just like {@link #release(Object)} but also disable debug tracking of the release.
   *
   * @return true if release was done
   */
  public static boolean releaseWithNoTracking(@Released Object o) {
    if (o instanceof StoredObject) {
      StoredObject so = (StoredObject) o;
      if (!so.hasRefCount()) {
        so.release();
        return true;
      }
      ReferenceCountHelper.skipRefCountTracking();
      so.release();
      ReferenceCountHelper.unskipRefCountTracking();
      return true;
    } else {
      return false;
    }
  }

  /**
   * Just like {@link #release(Object)} but also set the owner for debug tracking of the release.
   *
   * @return true if release was done
   */
  public static boolean releaseAndTrackOwner(@Released final Object o, final Object owner) {
    if (o instanceof StoredObject) {
      StoredObject so = (StoredObject) o;
      if (!so.hasRefCount()) {
        so.release();
        return true;
      }
      ReferenceCountHelper.setReferenceCountOwner(owner);
      so.release();
      ReferenceCountHelper.setReferenceCountOwner(null);
      return true;
    } else {
      return false;
    }
  }

}
