All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.sandius.rembulan.impl.ImmutableTable Maven / Gradle / Ivy
/*
* 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());
}
}
}