/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once //============================================================================== struct ClassDatabase { //============================================================================== struct MemberInfo { enum CodeLocationType { declaration = 0, addedToParent, setBoundsParamX, setBoundsParamY, setBoundsParamW, setBoundsParamH, // WARNING! When you change any of these, also update the copy that lives in the live editing code numCodeLocationTypes }; MemberInfo() {} MemberInfo (const MemberInfo& other) : name (other.name), type (other.type) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i] = other.locations[i]; } MemberInfo (const String& nm, const String& ty) : name (nm), type (ty) { } MemberInfo (const ValueTree& v) : name (v [Ids::name].toString()), type (v [Ids::class_].toString()) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i] = v [getIdentifierForCodeLocationType (i)].toString(); } const String& getName() const { return name; } const String& getType() const { return type; } const SourceCodeRange& getLocation (CodeLocationType t) const { return locations[t]; } void setLocation (CodeLocationType t, const SourceCodeRange& range) { locations[t] = range; } void mergeWith (const MemberInfo& other) { jassert (name == other.name); if (other.type.isNotEmpty()) type = other.type; for (int i = 0; i < numCodeLocationTypes; ++i) if (other.locations[i].isValid()) locations[i] = other.locations[i]; } void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].nudge (file, insertPoint, delta); } void fileContentChanged (const String& file) { for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].fileContentChanged (file); } ValueTree toValueTree() const { ValueTree m (Ids::MEMBER); m.setProperty (Ids::name, name, nullptr); m.setProperty (Ids::class_, type, nullptr); for (int i = 0; i < numCodeLocationTypes; ++i) locations[i].writeToValueTree (m, getIdentifierForCodeLocationType (i)); return m; } private: String name, type; SourceCodeRange locations [numCodeLocationTypes]; static Identifier getIdentifierForCodeLocationType (int typeIndex) { // (These need to remain in order) static_assert (setBoundsParamX + 1 == setBoundsParamY && setBoundsParamY + 1 == setBoundsParamW && setBoundsParamW + 1 == setBoundsParamH, ""); static const Identifier ids[] = { "declaration", "addedToParent", "setBoundsParamX", "setBoundsParamY", "setBoundsParamW", "setBoundsParamH" }; return ids [typeIndex]; } }; //============================================================================== struct MethodInfo { MethodInfo() {} MethodInfo (const MethodInfo& other) : name (other.name), returnType (other.returnType), declaration (other.declaration), definition (other.definition), numArgs (other.numArgs), flags (other.flags) { } String name, returnType; SourceCodeRange declaration, definition; int numArgs, flags; enum { isConstructor = 1, isDefaultConstructor = 2, isTemplated = 4, isPublic = 8 }; MethodInfo (const ValueTree& v) : name (v[Ids::name].toString()), returnType (v[Ids::returnType].toString()), declaration (v[Ids::declaration].toString()), definition (v[Ids::definition].toString()), numArgs (v[Ids::numArgs]), flags (v[Ids::flags]) { } ValueTree toValueTree() const { ValueTree m (Ids::METHOD); m.setProperty (Ids::name, name, nullptr); m.setProperty (Ids::returnType, returnType, nullptr); m.setProperty (Ids::numArgs, numArgs, nullptr); m.setProperty (Ids::flags, flags, nullptr); declaration.writeToValueTree (m, Ids::declaration); definition.writeToValueTree (m, Ids::definition); return m; } void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta) { declaration.nudge (file, insertPoint, delta); definition.nudge (file, insertPoint, delta); } void fileContentChanged (const String& file) { declaration.fileContentChanged (file); definition.fileContentChanged (file); } }; //============================================================================== struct InstantiationFlags { InstantiationFlags() : isAbstract (false), inAnonymousNamespace (false), noDefaultConstructor (false) {} bool canBeInstantiated() const noexcept { return ! (isAbstract || inAnonymousNamespace || noDefaultConstructor); } String getReasonForUnavailability() const { if (isAbstract) return "This class is abstract"; if (noDefaultConstructor) return "This class has no default constructor"; if (inAnonymousNamespace) return "This class is declared inside an anonymous namespace"; return String(); } bool isDisallowed (const InstantiationFlags& disallowedFlags) const { return ! ((disallowedFlags.isAbstract && isAbstract) || (disallowedFlags.inAnonymousNamespace && inAnonymousNamespace) || (disallowedFlags.noDefaultConstructor && noDefaultConstructor)); } bool isAbstract; bool inAnonymousNamespace; bool noDefaultConstructor; }; //============================================================================== struct Class { Class() {} ~Class() {} Class (const Class& other) : className (other.className), members (other.members), methods (other.methods), classDeclaration (other.classDeclaration), instantiationFlags (other.instantiationFlags) { } Class (const String& name, const InstantiationFlags& flags, const Array& m, const Array& meth, const SourceCodeRange& classDeclarationRange) : className (name), members (m), methods (meth), classDeclaration (classDeclarationRange), instantiationFlags (flags) { } Class& operator= (const Class& other) { className = other.className; members = other.members; methods = other.methods; classDeclaration = other.classDeclaration; instantiationFlags = other.instantiationFlags; return *this; } const String& getName() const noexcept { return className; } const InstantiationFlags& getInstantiationFlags() const { return instantiationFlags; } void setInstantiationFlags (const InstantiationFlags& newFlags) { instantiationFlags = newFlags; } const SourceCodeRange& getClassDeclarationRange() const { return classDeclaration; } const MemberInfo* findMember (const String& memberName) const { for (auto& m : members) if (m.getName() == memberName) return &m; return nullptr; } MemberInfo* findMember (const String& memberName) { return const_cast (static_cast(*this).findMember (memberName)); } const MethodInfo* getDefaultConstructor() const { for (const MethodInfo& m : methods) if ((m.flags & MethodInfo::isDefaultConstructor) != 0) return &m; return nullptr; } const MethodInfo* getConstructor() const { if (const MethodInfo* m = getDefaultConstructor()) return m; for (const MethodInfo& m : methods) if ((m.flags & MethodInfo::isConstructor) != 0) return &m; return nullptr; } const MethodInfo* getResizedMethod() const { for (const MethodInfo& m : methods) if (m.name == "resized" && m.numArgs == 0) return &m; return nullptr; } File getMainSourceFile() const { if (const MethodInfo* m = getResizedMethod()) if (m->definition.isValid()) return m->definition.file; if (const MethodInfo* m = getConstructor()) if (m->definition.isValid()) return m->definition.file; for (auto& m : methods) if (m.definition.isValid() && File (m.definition.file).hasFileExtension ("cpp;mm")) return m.definition.file; for (auto& m : methods) if ((m.flags & MethodInfo::isConstructor) != 0 && m.definition.isValid()) return m.definition.file; for (auto& m : methods) if (m.definition.isValid() && File (m.definition.file).exists()) return m.definition.file; return {}; } Array getAllSourceFiles() const { Array files; for (const MethodInfo& m : methods) { files.addIfNotAlreadyThere (m.declaration.file); files.addIfNotAlreadyThere (m.definition.file); } return files; } bool isDeclaredInFile (const File& file) const { return file == classDeclaration.file; } void mergeWith (const Class& other) { jassert (*this == other); if (other.classDeclaration.isValid()) classDeclaration = other.classDeclaration; for (auto& m : other.members) { if (auto* existing = findMember (m.getName())) existing->mergeWith (m); else members.add (m); } } void nudgeAllCodeRanges (const String& file, int index, int delta) { for (MemberInfo& m : members) m.nudgeAllCodeRanges (file, index, delta); for (MethodInfo& m : methods) m.nudgeAllCodeRanges (file, index, delta); classDeclaration.nudge (file, index, delta); } void fileContentChanged (const String& file) { for (MemberInfo& m : members) m.fileContentChanged (file); for (MethodInfo& m : methods) m.fileContentChanged (file); classDeclaration.fileContentChanged (file); } Class (const ValueTree& v) { className = v[Ids::name]; instantiationFlags.isAbstract = v[Ids::abstract]; instantiationFlags.inAnonymousNamespace = v[Ids::anonymous]; instantiationFlags.noDefaultConstructor = v[Ids::noDefConstructor]; classDeclaration = v [Ids::classDecl].toString(); for (int i = 0; i < v.getNumChildren(); ++i) members.add (MemberInfo (v.getChild(i))); } ValueTree toValueTree() const { ValueTree v (Ids::CLASS); v.setProperty (Ids::name, className, nullptr); v.setProperty (Ids::abstract, instantiationFlags.isAbstract, nullptr); v.setProperty (Ids::anonymous, instantiationFlags.inAnonymousNamespace, nullptr); v.setProperty (Ids::noDefConstructor, instantiationFlags.noDefaultConstructor, nullptr); classDeclaration.writeToValueTree (v, Ids::classDecl); for (const MemberInfo& m : members) v.appendChild (m.toValueTree(), nullptr); return v; } bool operator== (const Class& other) const noexcept { return className == other.className; } bool operator!= (const Class& other) const noexcept { return ! operator== (other); } bool operator< (const Class& other) const noexcept { return className < other.className; } const Array& getMembers() const { return members; } private: String className; Array members; Array methods; SourceCodeRange classDeclaration; InstantiationFlags instantiationFlags; JUCE_LEAK_DETECTOR (Class) JUCE_DECLARE_WEAK_REFERENCEABLE (Class) }; //============================================================================== struct Namespace { Namespace() : name ("Global Namespace") {} Namespace (const String& n, const String& full) : name (n), fullName (full) {} bool isEmpty() const noexcept { for (const auto& n : namespaces) if (! n.isEmpty()) return false; return components.size() == 0; } int getTotalClassesAndNamespaces() const { int total = components.size(); for (const auto& n : namespaces) total += n.getTotalClassesAndNamespaces(); return total; } void add (const Class& c, const String::CharPointerType& localName) { auto nextDoubleColon = CharacterFunctions::find (localName, CharPointer_ASCII ("::")); if (nextDoubleColon.isEmpty()) merge (c); else getOrCreateNamespace (String (localName, nextDoubleColon))->add (c, nextDoubleColon + 2); } bool containsRecursively (const Class& c) const { if (components.contains (c)) return true; for (const auto& n : namespaces) if (n.containsRecursively (c)) return true; return false; } const Class* findClass (const String& className) const { for (auto& c : components) if (c.getName() == className) return &c; for (auto& n : namespaces) if (auto* c = n.findClass (className)) return c; return nullptr; } const MemberInfo* findClassMemberInfo (const String& className, const String& memberName) const { if (auto* classInfo = findClass (className)) return classInfo->findMember (memberName); return nullptr; } void findClassesDeclaredInFile (Array>& results, const File& file) { for (int i = 0; i < components.size(); ++i) { auto& c = components.getReference (i); if (c.isDeclaredInFile (file)) results.add (&c); } for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).findClassesDeclaredInFile (results, file); } void merge (const Namespace& other) { if (components.size() == 0) { components = other.components; } else { for (const auto& c : other.components) merge (c); } for (const auto& n : other.namespaces) getOrCreateNamespace (n.name)->merge (n); } void merge (const Class& c) { const int existing = components.indexOf (c); if (existing < 0) components.add (c); else components.getReference (existing).mergeWith (c); } Namespace* findNamespace (const String& targetName) { for (int i = 0; i < namespaces.size(); ++i) { auto& n = namespaces.getReference (i); if (n.name == targetName) return &n; } return nullptr; } Namespace* createNamespace (const String& newName) { namespaces.add (Namespace (newName, fullName + "::" + newName)); return findNamespace (newName); } Namespace* getOrCreateNamespace (const String& newName) { if (auto* existing = findNamespace (newName)) return existing; return createNamespace (newName); } void addInstantiableClasses (SortedSet& classes) const { for (const auto& c : components) if (c.getInstantiationFlags().canBeInstantiated()) classes.add (c); for (const auto& n : namespaces) n.addInstantiableClasses (classes); } void swapWith (Namespace& other) noexcept { name.swapWith (other.name); components.swapWith (other.components); namespaces.swapWith (other.namespaces); } void nudgeAllCodeRanges (const String& file, int index, int delta) { for (int i = 0; i < components.size(); ++i) components.getReference (i).nudgeAllCodeRanges (file, index, delta); for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).nudgeAllCodeRanges (file, index, delta); } void fileContentChanged (const String& file) { for (int i = 0; i < components.size(); ++i) components.getReference (i).fileContentChanged (file); for (int i = 0; i < namespaces.size(); ++i) namespaces.getReference (i).fileContentChanged (file); } bool matches (const Namespace& other) const { if (name == other.name && components == other.components && namespaces.size() == other.namespaces.size()) { for (int i = namespaces.size(); --i >= 0;) if (! namespaces.getReference (i).matches (other.namespaces.getReference (i))) return false; return true; } return false; } void getAllClassNames (StringArray& results, const InstantiationFlags& disallowedFlags) const { for (const auto& c : components) if (c.getInstantiationFlags().isDisallowed (disallowedFlags)) results.add (c.getName()); for (const auto& n : namespaces) n.getAllClassNames (results, disallowedFlags); } ValueTree toValueTree() const { ValueTree v (Ids::CLASSLIST); v.setProperty (Ids::name, name, nullptr); for (const auto& c : components) v.appendChild (c.toValueTree(), nullptr); for (const auto& n : namespaces) v.appendChild (n.toValueTree(), nullptr); return v; } void loadFromValueTree (const ValueTree& v) { name = v[Ids::name]; for (int i = 0; i < v.getNumChildren(); ++i) { const ValueTree c (v.getChild(i)); if (c.hasType (Ids::CLASS)) components.add (Class (c)); else if (c.hasType (Ids::CLASSLIST)) createNamespace (c[Ids::name])->loadFromValueTree (c); } } bool operator== (const Namespace& other) const noexcept { return name == other.name; } bool operator!= (const Namespace& other) const noexcept { return ! operator== (other); } bool operator< (const Namespace& other) const noexcept { return name < other.name; } String name, fullName; SortedSet components; SortedSet namespaces; JUCE_LEAK_DETECTOR (Namespace) }; struct ClassList { ClassList() {} void clear() { Namespace newNamespace; globalNamespace.swapWith (newNamespace); } void registerComp (const Class& comp) { globalNamespace.add (comp, comp.getName().getCharPointer()); } void merge (const ClassList& other) { globalNamespace.merge (other.globalNamespace); } void swapWith (ClassList& other) noexcept { globalNamespace.swapWith (other.globalNamespace); } //============================================================================== ValueTree toValueTree() const { return globalNamespace.toValueTree(); } static ClassList fromValueTree (const ValueTree& v) { ClassList l; l.globalNamespace.loadFromValueTree (v); return l; } Namespace globalNamespace; bool operator== (const ClassList& other) const noexcept { return globalNamespace.matches (other.globalNamespace); } bool operator!= (const ClassList& other) const noexcept { return ! operator== (other); } private: JUCE_LEAK_DETECTOR (ClassList) }; };