com.glowinteractive.reforger.Item.java Source code

Java tutorial

Introduction

Here is the source code for com.glowinteractive.reforger.Item.java

Source

/**
 * @author Luke Tyler Downey
 * Copyright 2011 Glow Interactive
 *
 * This software contains original work and/or modifications to
 * original work, which are redistributed under the following terms.
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright 2011 Brian Cairns
 *
 * 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 com.glowinteractive.reforger;

import java.net.URL;

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringEscapeUtils;

import org.htmlcleaner.CleanerTransformations;
import org.htmlcleaner.CommentNode;
import org.htmlcleaner.ContentNode;
import org.htmlcleaner.HtmlNode;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.TagNodeVisitor;
import org.htmlcleaner.TagTransformation;

public final class Item implements Comparable<Item>, TagNodeVisitor {

    private static final String TOOLTIP_FORMAT = "tooltip_enus: '(.*)'";
    private static final String TOOLTIP_RATING_NON_RANDOM = "Equip\\: Increases your ([ \\w]+) rating by (\\d+)";
    private static final String TOOLTIP_RATING_RANDOM_OR_BONUS = "\\+?(\\d+) ([ \\w&&[^\\d]]+) [rR]ating";

    private boolean _parsed = false;

    private int _slot;
    private String _name;
    private TagNode _data;

    private final UUID _uniqueID;

    private StatKVMap _mutableStats;
    private StatKVMap _immutableStats;
    private StatKVMap _currentReforging;

    private Item() {
        _uniqueID = UUID.randomUUID();

        _mutableStats = new StatKVMap();
        _immutableStats = new StatKVMap();
        _currentReforging = new StatKVMap();
    }

    public Item(int slot, TagNode data) {
        this();

        _slot = slot;
        _data = data;
    }

    @Override
    public int compareTo(Item o) {
        return Integer.valueOf(_slot).compareTo(o._slot);
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Item) {
            Item o = (Item) other;
            return _uniqueID.equals(o._uniqueID);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return _uniqueID.hashCode();
    }

    @Override
    public String toString() {
        parse();

        // StatKVMap stats = _mutableStats.add(_immutableStats);
        // Previously: "[\"" + _name + "\" - Stats: " + stats + "]"
        return _name;
    }

    public synchronized void parse() {
        if (!_parsed) {
            String[] pair;
            String[] elements;
            String attribute;

            URL url = null;

            StringBuilder wowhead = new StringBuilder("http://www.wowhead.com/");
            TagNode ref = null;

            //<editor-fold defaultstate="collapsed" desc="Parse name.">
            ref = _data.findElementByAttValue("class", "name-shadow", true, true);
            assert ref != null && ref.getText() != null : "Error: unable to determine item name.";
            _name = (ref != null) ? StringEscapeUtils.unescapeHtml4(ref.getText().toString()) : "";
            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc="Parse item ID.">
            ref = _data.findElementByName("a", false);
            assert ref != null : "Error: unable to determine item attributes.";
            attribute = ref.getAttributeByName("href");
            elements = attribute.split("/wow/en/item/");
            assert elements.length == 2 : "Error: unexpected Armory data format.";
            wowhead.append("item=").append(elements[1]);
            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc="Extract data-item string.">
            ref = _data.findElementByName("a", false);
            assert ref != null : "Error: unable to determine item attributes.";
            attribute = ref.getAttributeByName("data-item");
            elements = StringEscapeUtils.unescapeHtml4((attribute != null) ? attribute : "").split("&");
            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc="Parse Armory data-item attributes.">
            for (String e : elements) {
                pair = e.split("=");

                if ("e".equals(pair[0])) {
                    // Permanent Enchantment
                    wowhead.append("&ench=").append(pair[1]);
                }

                if ("re".equals(pair[0])) {
                    // Reforge ID (not currently supported by Wowhead)
                    wowhead.append("&rf=").append(pair[1]);
                }

                if ("es".equals(pair[0])) {
                    // Additional Socket
                    wowhead.append("&sock");
                }

                if ("r".equals(pair[0])) {
                    // Random Itemization
                    wowhead.append("&rand=").append(pair[1]);
                }

                if ("set".equals(pair[0])) {
                    // Set Pieces Equipped
                    wowhead.append("&pcs=").append(pair[1].replace(',', ':'));
                }
            }
            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc="Parse Armory gem ID's.">
            TagNode[] gems = _data.getElementsByAttValue("class", "gem", true, true);
            final int GEM_COUNT = gems.length;

            if (GEM_COUNT != 0) {
                String suffix;

                wowhead.append("&gems=");

                for (int i = 0; i < GEM_COUNT; ++i) {
                    suffix = gems[i].getAttributeByName("href").replace("/wow/en/item/", "");
                    wowhead.append(suffix);
                    if (i + 1 < GEM_COUNT) {
                        wowhead.append(":");
                    }
                }
            }
            //</editor-fold>

            wowhead.append("&power");

            System.out.println("  " + _name);

            //<editor-fold defaultstate="collapsed" desc="Download and parse Wowhead JSON data.">
            try {
                url = new URL(wowhead.toString());
            } catch (Exception e) {
                Logger.getLogger(Item.class.getSimpleName()).log(Level.SEVERE, null, e);
            }

            Pattern p = Pattern.compile(TOOLTIP_FORMAT);
            Matcher m = p.matcher(URLRetriever.fetchContents(url));
            String itemPayload = (m.find()) ? m.group(1) : "";

            HtmlCleaner parser = new HtmlCleaner();
            CleanerTransformations transform = new CleanerTransformations();
            TagTransformation strip = new TagTransformation("small");
            transform.addTransformation(strip);
            parser.setTransformations(transform);

            TagNode root = parser.clean(itemPayload);
            //</editor-fold>

            root.traverse(this);

            System.out.println();

            _parsed = true;
        }
    }

    @Override
    public boolean visit(TagNode parent, HtmlNode current) {
        Pattern p = null;
        Matcher m = null;

        String text = null;
        Stat key = null;
        int value = -1;

        boolean PARSE_MUTABLE = false;

        int GROUP_INDEX_KEY = -1, GROUP_INDEX_VAL = -1;

        if (current instanceof CommentNode) {
            final CommentNode c = (CommentNode) current;
            final String content = c.getCommentedContent();

            if (content.matches("<!--rtg\\d\\d-->")) {
                //<editor-fold defaultstate="collapsed" desc="Secondary Stats for Non-Random Itemization Pieces.">
                //   e.g.: "Equip: Increases your critical strike rating by 168"

                GROUP_INDEX_KEY = 1;
                GROUP_INDEX_VAL = 2;

                PARSE_MUTABLE = true;

                text = parent.getText().toString().replace("&nbsp;", " ");
                p = Pattern.compile(TOOLTIP_RATING_NON_RANDOM);
                //</editor-fold>
            }

            if (content.matches("<!--ee-->")) {
                //<editor-fold defaultstate="collapsed" desc="Permanent enchant effects.">
                //   e.g.: "+190 Attack Power and +55 Critical Strike Rating"

                GROUP_INDEX_KEY = 2;
                GROUP_INDEX_VAL = 1;

                PARSE_MUTABLE = false;

                text = parent.getText().toString();
                p = Pattern.compile(TOOLTIP_RATING_RANDOM_OR_BONUS);
                //</editor-fold>
            }
        }

        if (current instanceof ContentNode) {
            final ContentNode c = (ContentNode) current;
            final String content = c.getContent().toString();

            if ("q1".equals(parent.getAttributeByName("class"))) {
                //<editor-fold defaultstate="collapsed" desc="Secondary Stats for Random Itemization Pieces.">
                //   e.g.: "+168 Critical Strike Rating"

                GROUP_INDEX_KEY = 2;
                GROUP_INDEX_VAL = 1;

                PARSE_MUTABLE = true;

                text = content;
                p = Pattern.compile(TOOLTIP_RATING_RANDOM_OR_BONUS);
                //</editor-fold>
            }

            if (parent.getAttributeByName("class") != null
                    && parent.getAttributeByName("class").startsWith("socket-")) {
                //<editor-fold defaultstate="collapsed" desc="Secondary Stats for gems.">
                //   e.g.: "+20 Agility and +20 Mastery Rating"

                GROUP_INDEX_KEY = 2;
                GROUP_INDEX_VAL = 1;

                PARSE_MUTABLE = false;

                text = content;
                p = Pattern.compile(TOOLTIP_RATING_RANDOM_OR_BONUS);
                //</editor-fold>
            }

            if (content.startsWith("Socket Bonus") && "q2".equals(parent.getAttributeByName("class"))) {
                //<editor-fold defaultstate="collapsed" desc="Secondary Stats for Socket Bonuses.">
                //   e.g.: "Socket Bonus: +30 Haste"

                GROUP_INDEX_KEY = 2;
                GROUP_INDEX_VAL = 1;

                PARSE_MUTABLE = false;

                text = content;
                p = Pattern.compile(TOOLTIP_RATING_RANDOM_OR_BONUS);
                //</editor-fold>
            }
        }

        if (text == null) {
            // Didn't match any known constructs.
            return true;
        }

        m = p.matcher(StringEscapeUtils.unescapeHtml4(text));

        while (m.find()) {
            key = null;
            value = Integer.parseInt(m.group(GROUP_INDEX_VAL));

            for (Stat s : Stat.values()) {
                if (s.shortName().equalsIgnoreCase(m.group(GROUP_INDEX_KEY))) {
                    key = s;
                }
            }

            // TODO: Refactor output to parse() via mutable / immutable extension to StatKVMap.
            if (key != null) {
                if (PARSE_MUTABLE) {
                    _mutableStats = _mutableStats.add(new StatKVPair(key, value));
                    System.out.println(String.format("    %+5d", value) + " " + key.shortName());
                } else {
                    _immutableStats = _immutableStats.add(new StatKVPair(key, value));
                    System.out.println(String.format("    %+5d", value) + " " + key.shortName() + " [Immutable]");
                }
            }
        }

        return true;
    }

    public StatKVMap mutableStats() {
        parse();
        return _mutableStats;
    }

    public StatKVMap immutableStats() {
        parse();
        return _immutableStats;
    }

    public StatKVMap currentReforging() {
        parse();
        return _currentReforging;
    }

    public String name() {
        parse();
        return _name;
    }

    public int slot() {
        return _slot;
    }

    public HashSet<StatKVMap> candidates(EnumMap<Stat, EnumSet<Stat>> mappings) {
        parse();

        final HashSet<StatKVMap> result = new HashSet<StatKVMap>(Stat.TYPE_COUNT);

        for (Stat decrease : Stat.values()) {
            for (Stat increase : Stat.values()) {
                if (_mutableStats.value(increase) == 0 && _mutableStats.value(decrease) != 0
                        && mappings.containsKey(decrease) && mappings.get(decrease).contains(increase)) {
                    int delta = Math.round((float) Math.floor(0.4 * _mutableStats.value(decrease)));
                    StatKVMap deltaMap = new StatKVMap(decrease, increase, delta);
                    result.add(deltaMap);
                }
            }
        }

        return result;
    }
}