All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sandius.rembulan.impl.ImmutableTable Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
/*
 * Copyright 2016 Miroslav Janíček
 *
 * Licensed 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 net.sandius.rembulan.impl;

import net.sandius.rembulan.Conversions;
import net.sandius.rembulan.Table;
import net.sandius.rembulan.TableFactory;
import net.sandius.rembulan.util.TraversableHashMap;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * An immutable table.
 *
 * 

The contents of this table may be queried, but not changed: the methods * {@link #rawset(Object, Object)}, {@link #rawset(long, Object)} and {@link #setMetatable(Table)} * will throw an {@link UnsupportedOperationException}.

* *

The table has no metatable.

* *

To instantiate a new {@code ImmutableTable}, use one of the static constructor methods * (e.g., {@link #of(Iterable)}), or a {@link ImmutableTable.Builder} as follows:

* *
 *     ImmutableTable t = new ImmutableTable.Builder()
 *         .add("key1", "value1")
 *         .add("key2", "value2")
 *         .build();
 * 
* *

A word of caution: this class violates the expectation that all Lua tables are * mutable, and should therefore be used with care. In order to create a mutable copy of this * table, use {@link #newCopy(TableFactory)}.

*/ public class ImmutableTable extends Table { private final Map entries; private final Object initialKey; // null iff the table is empty static class Entry { private final Object value; private final Object nextKey; // may be null private Entry(Object value, Object nextKey) { this.value = Objects.requireNonNull(value); this.nextKey = nextKey; } } ImmutableTable(Map entries, Object initialKey) { this.entries = Objects.requireNonNull(entries); this.initialKey = initialKey; } /** * Returns an {@code ImmutableTable} based on the contents of the sequence of * map entries {@code entries}. * *

For every {@code key}-{@code value} pair in {@code entries}, the behaviour of this * method is similar to that of {@link Table#rawset(Object, Object)}:

*
    *
  • when {@code value} is nil (i.e., {@code null}), then {@code key} * will not have any value associated with it in the resulting table;
  • *
  • if {@code key} is nil or NaN, a {@link IllegalArgumentException} * is thrown;
  • *
  • if {@code key} is a number that has an integer value, it is converted to that integer * value.
  • *
* *

Keys may occur multiple times in {@code entries} — only the last occurrence * counts.

* * @param entries the map entries, must not be {@code null} * @return an immutable table based on the contents of {@code entries} * * @throws NullPointerException if {@code entries} is {@code null} * @throws IllegalArgumentException if {@code entries} contains an entry with * a {@code null} or NaN key */ public static ImmutableTable of(Iterable> entries) { Builder builder = new Builder(); for (Map.Entry entry : entries) { builder.add(entry.getKey(), entry.getValue()); } return builder.build(); } /** * Returns an {@code ImmutableTable} based on the contents of the map {@code map}. * *

For every {@code key}-{@code value} pair in {@code map}, the behaviour of this method * is similar to that of {@link Table#rawset(Object, Object)}:

*
    *
  • when {@code value} is nil (i.e., {@code null}), then {@code key} * will not have any value associated with it in the resulting table;
  • *
  • if {@code key} is nil or NaN, a {@link IllegalArgumentException} * is thrown;
  • *
  • if {@code key} is a number that has an integer value, it is converted to that integer * value.
  • *
* * @param map the map used to source the contents of the table, must not be {@code null} * @return an immutable table based on the contents of {@code map} * * @throws NullPointerException if {@code entries} is {@code null} * @throws IllegalArgumentException if {@code map} contains a {@code null} or NaN key */ public static ImmutableTable of(Map map) { return of(map.entrySet()); } /** * Returns a new table constructed using the supplied {@code tableFactory}, and copies * the contents of this table to it. * * @param tableFactory the table factory to use, must not be {@code null} * @return a mutable copy of this table */ public Table newCopy(TableFactory tableFactory) { Table t = tableFactory.newTable(); for (Object key : entries.keySet()) { Entry e = entries.get(key); t.rawset(key, e.value); } return t; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ImmutableTable that = (ImmutableTable) o; return this.entries.equals(that.entries) && this.initialKey.equals(that.initialKey); } @Override public int hashCode() { int result = entries.hashCode(); result = 31 * result + initialKey.hashCode(); return result; } @Override public Object rawget(Object key) { key = Conversions.normaliseKey(key); Entry e = entries.get(key); return e != null ? e.value : null; } /** * Throws an {@link UnsupportedOperationException}, since this table is immutable. * * @param key ignored * @param value ignored * * @throws UnsupportedOperationException every time this method is called */ @Override public void rawset(Object key, Object value) { throw new UnsupportedOperationException("table is immutable"); } /** * Throws an {@link UnsupportedOperationException}, since this table is immutable. * * @param idx ignored * @param value ignored * * @throws UnsupportedOperationException every time this method is called */ @Override public void rawset(long idx, Object value) { throw new UnsupportedOperationException("table is immutable"); } @Override public Table getMetatable() { return null; } /** * Throws an {@link UnsupportedOperationException}, since this table is immutable. * * @param mt ignored * @return nothing (always throws an exception) * * @throws UnsupportedOperationException every time this method is called */ @Override public Table setMetatable(Table mt) { throw new UnsupportedOperationException("table is immutable"); } @Override public Object initialKey() { return initialKey; } @Override public Object successorKeyOf(Object key) { key = Conversions.normaliseKey(key); try { Entry e = entries.get(key); return e.nextKey; } catch (NullPointerException ex) { throw new IllegalArgumentException("invalid key to 'next'", ex); } } @Override protected void setMode(boolean weakKeys, boolean weakValues) { // no-op } /** * Builder class for constructing instances of {@link ImmutableTable}. */ public static class Builder { private final TraversableHashMap entries; private static void checkKey(Object key) { if (key == null || (key instanceof Double && Double.isNaN(((Double) key).doubleValue()))) { throw new IllegalArgumentException("invalid table key: " + Conversions.toHumanReadableString(key)); } } private Builder(TraversableHashMap entries) { this.entries = Objects.requireNonNull(entries); } /** * Constructs a new empty builder. */ public Builder() { this(new TraversableHashMap<>()); } private static TraversableHashMap mapCopy(TraversableHashMap map) { TraversableHashMap result = new TraversableHashMap<>(); result.putAll(map); return result; } /** * Constructs a copy of the given builder (a copy constructor). * * @param builder the original builder, must not be {@code null} * * @throws NullPointerException if {@code builder} is {@code null} */ public Builder(Builder builder) { this(mapCopy(builder.entries)); } /** * Sets the value associated with the key {@code key} to {@code value}. * *

The behaviour of this method is similar to that of * {@link Table#rawset(Object, Object)}:

*
    *
  • when {@code value} is nil (i.e., {@code null}), the key {@code key} * will not have any value associated with it after this method returns;
  • *
  • nil and NaN keys are rejected by throwing * a {@link IllegalArgumentException};
  • *
  • numeric keys with an integer value are converted to that integer value.
  • *
* *

The method returns {@code this}, allowing calls to this method to be chained.

* * @param key the key, must not be {@code null} or NaN * @param value the value, may be {@code null} * @return this builder * * @throws IllegalArgumentException when {@code key} is {@code null} or a NaN */ public Builder add(Object key, Object value) { key = Conversions.normaliseKey(key); checkKey(key); if (value != null) { entries.put(key, value); } else { entries.remove(key); } return this; } /** * Clears the builder. */ public void clear() { entries.clear(); } /** * Constructs and returns a new immutable table based on the contents of this * builder. * * @return a new immutable table */ public ImmutableTable build() { Map tableEntries = new HashMap<>(); for (Map.Entry e : entries.entrySet()) { Object k = e.getKey(); tableEntries.put(e.getKey(), new Entry(e.getValue(), entries.getSuccessorOf(k))); } return new ImmutableTable(Collections.unmodifiableMap(tableEntries), entries.getFirstKey()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy