001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package net.tridentsdk.server.data;
018
019import net.tridentsdk.base.Block;
020import net.tridentsdk.base.Substance;
021import net.tridentsdk.meta.block.BlockMeta;
022import net.tridentsdk.meta.component.*;
023import net.tridentsdk.server.data.block.*;
024import net.tridentsdk.util.Value;
025
026import java.util.Collection;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentSkipListSet;
031import java.util.stream.Collectors;
032
033/**
034 * Provides metadata implementations to the API
035 *
036 * @author The TridentSDK Team
037 */
038public class MetaProviderFactory implements MetaProvider {
039    private final MetaCollection collection = MetaFactory.newCollection();
040    private final Map<Substance, MetaCompiler> metaMap = new ConcurrentHashMap<>();
041
042    public MetaProviderFactory() {
043        register(new ColorMetaImpl());
044        register(new SignMetaImpl());
045        register(new FurnaceMetaImpl());
046        register(new ChestMetaImpl());
047        register(new DirectionMetaImpl());
048        register(new CauldronMetaImpl());
049    }
050
051    public boolean hasData(Substance substance) {
052        return metaMap.containsKey(substance);
053    }
054
055    @Override
056    public boolean decode(Block block, Value<Substance> substance, byte[] data, Value<Byte> result) {
057        MetaCompiler compiler = metaMap.get(substance.get());
058        if (compiler != null) {
059            byte metaByte = 0;
060            for (Meta<?> meta : compiler.compileBlock(block, data)) {
061                if (meta instanceof IllegalMeta) {
062                    block.clearMeta();
063                    return false;
064                }
065
066                block.applyMeta((BlockMeta) meta);
067                metaByte |= meta.encodeMeta()[0];
068            }
069
070            result.set(metaByte);
071            substance.set(block.substance());
072        } else {
073            // If no data was found for an item, the item is not placeable
074            if (!substance.get().isBlock()) {
075                return false;
076            }
077        }
078
079        return true;
080    }
081
082    @Override
083    public <S, T extends Meta<S>> T provide(Class<T> cls) {
084        return (T) collection.get(cls).make();
085    }
086
087    @Override
088    public void register(Meta meta) {
089        Substance[] substances = meta.applyTo(collection);
090        for (Substance substance : substances) {
091            MetaCompiler metaCompiler = metaMap.get(substance);
092            if (metaCompiler == null) {
093                metaCompiler = new MetaCompiler();
094            }
095
096            metaCompiler.add(meta);
097            metaMap.put(substance, metaCompiler);
098        }
099    }
100
101    private class MetaCompiler {
102        private final Set<Meta> metas = new ConcurrentSkipListSet<>((m1, m2) -> {
103            if (m1 instanceof BlockMeta && m2 instanceof BlockMeta) {
104                BlockMeta<?> b1 = ((BlockMeta) m1);
105                BlockMeta<?> b2 = ((BlockMeta) m2);
106
107                for (Class c : b1.dependencies()) {
108                    if (c.isInstance(b2)) {
109                        return 1;
110                    }
111                }
112
113                for (Class c : b2.dependencies()) {
114                    if (c.isInstance(b1)) {
115                        return -1;
116                    }
117                }
118            }
119
120            return 0;
121        });
122
123        public void add(Meta meta) {
124            metas.add(meta);
125        }
126
127        public Collection<Meta> compileBlock(Block block, byte[] data) {
128            return metas.stream()
129                    .map((meta) -> meta.decodeMeta(block, data))
130                    .collect(Collectors.toList());
131        }
132    }
133}