001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.plot.flag.types;
020
021import com.google.common.base.Objects;
022import com.google.common.base.Preconditions;
023import com.plotsquared.core.configuration.Settings;
024import com.sk89q.worldedit.world.block.BlockCategory;
025import com.sk89q.worldedit.world.block.BlockStateHolder;
026import com.sk89q.worldedit.world.block.BlockType;
027import org.apache.logging.log4j.LogManager;
028import org.apache.logging.log4j.Logger;
029import org.checkerframework.checker.nullness.qual.NonNull;
030import org.checkerframework.checker.nullness.qual.Nullable;
031
032import java.util.HashMap;
033import java.util.Map;
034
035/**
036 * Container that class either contains a {@link BlockType}
037 * or a {@link BlockCategory}
038 */
039public class BlockTypeWrapper {
040
041    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BlockTypeWrapper.class.getSimpleName());
042
043    private static final Map<BlockType, BlockTypeWrapper> blockTypes = new HashMap<>();
044    private static final Map<String, BlockTypeWrapper> blockCategories = new HashMap<>();
045    private static final String minecraftNamespace = "minecraft";
046    @Nullable
047    private final BlockType blockType;
048    @Nullable
049    private final String blockCategoryId;
050    @Nullable
051    private BlockCategory blockCategory;
052
053    private BlockTypeWrapper(final @NonNull BlockType blockType) {
054        this.blockType = Preconditions.checkNotNull(blockType);
055        this.blockCategory = null;
056        this.blockCategoryId = null;
057    }
058
059    private BlockTypeWrapper(final @NonNull BlockCategory blockCategory) {
060        this.blockType = null;
061        this.blockCategory = Preconditions.checkNotNull(blockCategory);
062        this.blockCategoryId = blockCategory.getId(); // used in toString()/equals()/hashCode()
063    }
064
065    private BlockTypeWrapper(final @NonNull String blockCategoryId) {
066        this.blockType = null;
067        this.blockCategory = null;
068        this.blockCategoryId = Preconditions.checkNotNull(blockCategoryId);
069    }
070
071    public static BlockTypeWrapper get(final BlockType blockType) {
072        return blockTypes.computeIfAbsent(blockType, BlockTypeWrapper::new);
073    }
074
075    public static BlockTypeWrapper get(final BlockCategory blockCategory) {
076        return blockCategories
077                .computeIfAbsent(blockCategory.getId(), id -> new BlockTypeWrapper(blockCategory));
078    }
079
080    public static BlockTypeWrapper get(final String blockCategoryId) {
081        // use minecraft as default namespace
082        String id;
083        if (blockCategoryId.indexOf(':') == -1) {
084            id = minecraftNamespace + ":" + blockCategoryId;
085        } else {
086            id = blockCategoryId;
087        }
088        return blockCategories.computeIfAbsent(id, BlockTypeWrapper::new);
089    }
090
091    @Override
092    public String toString() {
093        if (this.blockType != null) {
094            final String key = this.blockType.toString();
095            if (key.startsWith("minecraft:")) {
096                return key.substring(10);
097            } else {
098                return key;
099            }
100        } else if (this.blockCategoryId != null) {
101            final String key = this.blockCategoryId;
102            if (key.startsWith("minecraft:")) {
103                return '#' + key.substring(10);
104            } else {
105                return '#' + key;
106            }
107        } else {
108            return null;
109        }
110    }
111
112    public boolean accepts(final BlockType blockType) {
113        if (this.getBlockType() != null) {
114            return this.getBlockType().equals(blockType);
115        } else if (this.getBlockCategory() != null) {
116            return this.getBlockCategory().contains(blockType);
117        } else {
118            return false;
119        }
120    }
121
122    /**
123     * Returns the block category associated with this wrapper.
124     * <br>
125     * Invocation will try to lazily initialize the block category if it's not
126     * set yet but the category id is present. If {@link BlockCategory#REGISTRY} is already populated
127     * but does not contain a category with the given name, a BlockCategory containing no items
128     * is returned.
129     * If this wrapper does not wrap a BlockCategory, null is returned.
130     * <br>
131     * <b>If {@link BlockCategory#REGISTRY} isn't populated yet, null is returned.</b>
132     *
133     * @return the block category represented by this wrapper.
134     */
135    public @Nullable BlockCategory getBlockCategory() {
136        if (this.blockCategory == null
137                && this.blockCategoryId != null) { // only if name is available
138            this.blockCategory = BlockCategory.REGISTRY.get(this.blockCategoryId);
139            if (this.blockCategory == null && !BlockCategory.REGISTRY.values().isEmpty()) {
140                if (Settings.DEBUG) {
141                    LOGGER.info("- Block category #{} does not exist", this.blockCategoryId);
142                }
143                this.blockCategory = new NullBlockCategory(this.blockCategoryId);
144            }
145        }
146        return this.blockCategory;
147    }
148
149    @Override
150    public boolean equals(final Object o) {
151        if (this == o) {
152            return true;
153        }
154        if (o == null || getClass() != o.getClass()) {
155            return false;
156        }
157        BlockTypeWrapper that = (BlockTypeWrapper) o;
158        return Objects.equal(this.blockType, that.blockType) && Objects
159                .equal(this.blockCategoryId, that.blockCategoryId);
160    }
161
162    @Override
163    public int hashCode() {
164        return Objects.hashCode(this.blockType, this.blockCategoryId);
165    }
166
167    public @Nullable BlockType getBlockType() {
168        return this.blockType;
169    }
170
171
172    /**
173     * Prevents exceptions when loading/saving block categories
174     */
175    private static class NullBlockCategory extends BlockCategory {
176
177        public NullBlockCategory(String id) {
178            super(id);
179        }
180
181        @Override
182        public <B extends BlockStateHolder<B>> boolean contains(B blockStateHolder) {
183            return false;
184        }
185
186    }
187
188}